Conversation
| @@ -1,2 +1,107 @@ | |||
| # minimalism | |||
| Implementation of A Formalization of Minimalist Syntax (Collins & Stabler, 2016) | |||
| # Parser minimalista | |||
There was a problem hiding this comment.
Tené en cuenta que haciendo las instrucciones en español en el mismo archivo README de él, no vas a poder hacerle una pr, si ese fuera tu interés.
|
|
||
| ### La gramática | ||
| Por defecto, el Lexicon de la gramática se genera a partir del archivo `lexicon.xml` de la carpeta `data`. La elección de este archivo puede ser modificada mediante la opción 3 del menú principal.<br> | ||
| Las gramáticas están definidas en el formato XML, que organiza el léxico como un árbol de nodos. A cada ítem le corresponde un elemento con la etiqueta `<word>`, dentro del cual se insertan nuevos elementos con una etiqueta distinta para cada rasgo: la etiqueta `<phon>` corresponde a los rasgos fonético, `<syn>` a los sintácticos, y `<sem>` a los semánticos (aunque estos últimos no son utilizados en la implementación actual). Por ejemplo, la entrada léxica correspondiente a un verbo como *corrió* sería la siguiente: |
There was a problem hiding this comment.
| Las gramáticas están definidas en el formato XML, que organiza el léxico como un árbol de nodos. A cada ítem le corresponde un elemento con la etiqueta `<word>`, dentro del cual se insertan nuevos elementos con una etiqueta distinta para cada rasgo: la etiqueta `<phon>` corresponde a los rasgos fonético, `<syn>` a los sintácticos, y `<sem>` a los semánticos (aunque estos últimos no son utilizados en la implementación actual). Por ejemplo, la entrada léxica correspondiente a un verbo como *corrió* sería la siguiente: | |
| Las gramáticas están definidas en el formato XML, que organiza el léxico como un árbol de nodos. A cada ítem le corresponde un elemento con la etiqueta `<word>`, dentro del cual se insertan nuevos elementos con una etiqueta distinta para cada rasgo: la etiqueta `<phon>` corresponde a los rasgos fonéticos, `<syn>` a los sintácticos, y `<sem>` a los semánticos (aunque estos últimos no son utilizados en la implementación actual). Por ejemplo, la entrada léxica correspondiente a un verbo como *llegó* sería la siguiente: |
| **5. Quit** *(Finaliza el programa)* | ||
|
|
||
| ### La gramática | ||
| Por defecto, el Lexicon de la gramática se genera a partir del archivo `lexicon.xml` de la carpeta `data`. La elección de este archivo puede ser modificada mediante la opción 3 del menú principal.<br> |
There was a problem hiding this comment.
| Por defecto, el Lexicon de la gramática se genera a partir del archivo `lexicon.xml` de la carpeta `data`. La elección de este archivo puede ser modificada mediante la opción 3 del menú principal.<br> | |
| Por defecto, el léxico de la gramática se genera a partir del archivo `lexicon.xml` de la carpeta `data`. La elección de este archivo puede ser modificada mediante la opción 3 del menú principal.<br> |
| #### Merge interno | ||
| Los rasgos de selección pueden ir acompañados de una **barra al final** (`D/`). Este operador le indica a la gramática que el ítem léxico en cuestión se somete a merge interno junto a otro ítem de la categoría declarada (en este caso, *llegó* primero se combina mediante merge externo con un determinante, y luego se combina con este determinante mediante merge interno, lo que en la práctica supone que el D se "mueva" o más bien se copie en la posición de especificador del SV). Este operador hace que el merge interno sólo pueda efectuarse con ciertos ítems léxicos, y una vez que los demás rasgos de selección ya fueron cotejados. Fue necesario añadir esta restricción, que no aparece en la implementación original, con el fin de subsanar el hecho de que los rasgos de selección (y todos los rasgos) constituyen un conjunto, y por lo tanto no están ordenados. | ||
| #### Categorías funcionales | ||
| Para indicar que un item léxico es una categoría funcional se necesitan dos cosas: 1) el contenido del rasgo fonético debe aparecer entre corchetes, y 2) la categoría del primer elemento con el que se combina debe estar precedida por una barra. La **barra al comienzo** (`/V`) permite que el algoritmo sepa en qué momento de la derivación debe seleccionarse la categoría funcional. Por ejemplo, así quedaría la entrada léxica de un *v* que se pudiera combinar con *llegó*: |
There was a problem hiding this comment.
| Para indicar que un item léxico es una categoría funcional se necesitan dos cosas: 1) el contenido del rasgo fonético debe aparecer entre corchetes, y 2) la categoría del primer elemento con el que se combina debe estar precedida por una barra. La **barra al comienzo** (`/V`) permite que el algoritmo sepa en qué momento de la derivación debe seleccionarse la categoría funcional. Por ejemplo, así quedaría la entrada léxica de un *v* que se pudiera combinar con *llegó*: | |
| Para indicar que un ítem léxico es una categoría funcional se necesitan dos cosas: 1) el contenido del rasgo fonético debe aparecer entre corchetes, y 2) la categoría del primer elemento con el que se combina debe estar precedida por una barra. La **barra al comienzo** (`/V`) permite que el algoritmo sepa en qué momento de la derivación debe seleccionarse la categoría funcional. Por ejemplo, así quedaría la entrada léxica de un *v* que se pudiera combinar con *llegó*: |
|  | ||
|
|
||
| Como se ve, es posible definir una gramática de tal modo que dos o más categorías funcionales se combinen entre sí. | ||
| <br>También es posible declarar dos ítems con los mismos rasgos fonéticos y categoriales, pero diferentes rasgos de selección. En este caso, el programa generará dos lexical arrays distintos, e imprimirá todas las derivaciones exitosas. |
There was a problem hiding this comment.
| <br>También es posible declarar dos ítems con los mismos rasgos fonéticos y categoriales, pero diferentes rasgos de selección. En este caso, el programa generará dos lexical arrays distintos, e imprimirá todas las derivaciones exitosas. | |
| <br>También es posible declarar dos ítems con los mismos rasgos fonéticos y categoriales, pero diferentes rasgos de selección. En este caso, el programa generará dos *lexical arrays* distintos, e imprimirá todas las derivaciones exitosas. |
| | N° | If len(x.triggers) | & len(workspace.w) | & len(lexical_array.the_list) | then: | ||
| | ------------- |:-------------:| -------- | --------- | ------- | | ||
| | 0 | | | | select(x) | ||
| | 1 | 0 | 1 | 0 | *success* |
There was a problem hiding this comment.
No terminamos de entender esta tabla, si hay una diferencia entre el uso de los iguales y los casos en que no hay un igual.
| @@ -1,34 +1,107 @@ | |||
| <?xml version='1.0' ?> | |||
There was a problem hiding this comment.
Si querés hacerle la PR a Warstadt, correspondería que no pises tu lexicon con el de él. Quizás conviene que llames lexicon_en al de él y lexicon_es al tuyo y que dejes como versión por defecto el de él en lugar del tuyo.
| La operación Transfer, tal como se encuentra definida en el trabajo de Collins-Stabler, interviene en el pasaje entre la estructura sintáctica y las formas fonética (Transfer<sub>PF</sub>) y semántica (Transfer<sub>LF</sub>). Nuestro programa implementa una versión simplificada de Transfer<sub>PF</sub>, que se aplica a toda derivación exitosa mediante la función recursiva `transfer()`. Este algoritmo opera de la siguiente manera: | ||
|
|
||
| * Si *x* e *y* son ítems léxicos (i.e., objetos sintácticos simples), se añaden a la lista `transferred_sentence` sus respectivos rasgos `Phon`, en este orden: primero el elemento con rasgos de selección (es decir, el núcleo) y luego el elemento seleccionado por aquél (que será el complemento). | ||
| * Si *x* es núcleo pero, al mismo tiempo, un conjunto de objetos sintácticos (i.e., un objeto sintáctico complejo), entonces se añade a la lista en primer lugar el rasgo `Phon` de *y* (especificador), en caso de que éste sea un item léxico, y luego se aplica nuevamente `transfer()` sobre *x*. En cambio, si *y* también es un objeto complejo, entonces se aplica `transfer()` primeramente a *y*, y luego a *x*. |
There was a problem hiding this comment.
i.e. es un latinismo crudo, así que técnicamente debería ir por normativa en itálicas. No es, de todos modos, una normativa muy extendida, así que queda a tu criterio. La normativa que abolió la tilde de "este" en todos sus usos sí está bastante aceptada, así que eso sugeriría cambiarlo.
| Nótese que `12,V` es un objeto complejo, y que por lo tanto en `13,V`, `10,P` es especificador. Esto supone que la oración generada por `transfer()` será *el perro al dueño entregó el hueso*. A su vez, un merge interno entre `12,V` y `13,V` resulta imposible, ya que el primer objeto tiene rasgos de selección sin satisfacer. El objeto `14,V` sí podría combinarse con `13,V`, pero sólo después del merge externo con `15,D`, por lo que el sintagma verbal quedaría por encima del argumento externo. De todos modos, un Lexicon con rasgos de selección y/o categorías funcionales distintas podría llegar a dar cuenta de esta oración sin necesidad de modificar el código. | ||
|
|
||
| ### A futuro | ||
| Elegimos el parser bottom-up porque es el que mejor se adapta a la implementación y el formalismo originales. Tal vez sería posible mejorarlo mediante la adición de nuevos operadores (como una o varias categorías iniciales), y permitiendo que se combinen elementos independientemente del orden lineal. |
There was a problem hiding this comment.
| Elegimos el parser bottom-up porque es el que mejor se adapta a la implementación y el formalismo originales. Tal vez sería posible mejorarlo mediante la adición de nuevos operadores (como una o varias categorías iniciales), y permitiendo que se combinen elementos independientemente del orden lineal. | |
| Elegimos el parser *bottom-up* porque es el que mejor se adapta a la implementación y el formalismo originales. Tal vez sería posible mejorarlo mediante la adición de nuevos operadores (como una o varias categorías iniciales), y permitiendo que se combinen elementos independientemente del orden lineal. |
| ### A futuro | ||
| Elegimos el parser bottom-up porque es el que mejor se adapta a la implementación y el formalismo originales. Tal vez sería posible mejorarlo mediante la adición de nuevos operadores (como una o varias categorías iniciales), y permitiendo que se combinen elementos independientemente del orden lineal. | ||
|
|
||
| Otra alternativa sería recurrir a un parser top-down capaz de generar todas las oraciones posibles de una gramática, y luego evaluar si la oración ingresada por el usuario coincide con alguna de ellas. Para esto, podríamos seguir el mecanismo descrito por [Shieber et al. (2005)](https://arxiv.org/abs/cmp-lg/9404008): |
There was a problem hiding this comment.
| Otra alternativa sería recurrir a un parser top-down capaz de generar todas las oraciones posibles de una gramática, y luego evaluar si la oración ingresada por el usuario coincide con alguna de ellas. Para esto, podríamos seguir el mecanismo descrito por [Shieber et al. (2005)](https://arxiv.org/abs/cmp-lg/9404008): | |
| Otra alternativa sería recurrir a un parser *top-down* capaz de generar todas las oraciones posibles de una gramática, y luego evaluar si la oración ingresada por el usuario coincide con alguna de ellas. Para esto, podríamos seguir el mecanismo descrito por [Shieber et al. (2005)](https://arxiv.org/abs/cmp-lg/9404008): |
|
|
||
| def manual_derivation(filename=filename): | ||
| lexicon = Lexicon(filename) | ||
| counter = 0 |
There was a problem hiding this comment.
Esta variable no se usa nunca. Se puede borrar. Sé que no la creaste vos, pero de todos modos es innecesaria.
| print(f"{key}: '{value}'") | ||
| return indexes | ||
|
|
||
| def manual_derivation(filename=filename): |
There was a problem hiding this comment.
Creo que sería mejor que manual_derivation esté en algún archivo dentro de derivations. Es cierto que antes esta función estaba acá, pero porque no había mucho más. Si ahora va haber más funcionalidad incluida, por ahí se puede agregar un archivo dentro de derivations que tenga las funciones que ejecuten manual_derivation y automated_derivation (o parse).
| full_path = os.path.abspath("data") | ||
| print(full_path) | ||
| filenames = [f for f in os.listdir(full_path) if os.path.isfile(os.path.join(full_path, f))] | ||
| for f in filenames: |
There was a problem hiding this comment.
Entiendo que desde acá hasta la línea 22 la intención es imprimir los archivos de gramáticas disponibles indicando cuál está seleccionado actualmente y cuántos son en general. La implementación que está ahora funciona y no está mal, pero te dejo un comentario para seguir pensando.
Fijate que ahora estás esjecutando 3 for loops: uno para modificar el orden de la lista y que el archivo seleccionado por defecto esté primero, otro para construir el diccionario de indexes y, por último, un tercero para imprimir. Eso se podría hacer usando un solo for loop:
print(f'{len(filenames)} grammar(s) found: ')
indexes = dict()
for i, file in enumerate(filenames):
if file == filename:
print(f'{i}: {file} (current)')
else:
print(f'{i}: {file}')
indexes[i] = file
Perdés la posibilidad de que el archivo seleccionado por defecto quede arriba del todo, pero por ahí no es tan grave. Pensalo.
| global filename | ||
| full_path = os.path.abspath("data") | ||
| print(full_path) | ||
| filenames = [f for f in os.listdir(full_path) if os.path.isfile(os.path.join(full_path, f))] |
There was a problem hiding this comment.
Consideraría la posibilidad de filtrar por extensión de archivo. Pensá que si mañana querés agregarun README.mda la carpeta data para que futuros usuarios sepan cómo construir una gramática, ese README aparecería listado porque es un file. Si lo filtrás por extensión, reducís ese riesgo.
| filenames = [f for f in os.listdir(full_path) if os.path.isfile(os.path.join(full_path, f))] | |
| filenames = [f for f in os.listdir(full_path) if f.endswith('.xml')] |
| elif user_input == '2': | ||
| manual_derivation(filename) | ||
| elif user_input == '3': | ||
| while True: |
There was a problem hiding this comment.
Creo que todo esto se podría englobar dentro de una única opción select_grammar que implementase esta lógica de selección. Sería como agregarle una capa más después de print_grammars, y que main llame directo a la función seleccionadora. Evaluaría hacer lo mismo con el AssertionError de la opción 1.
There was a problem hiding this comment.
Perdón, quise poner función en lugar de opción y por algún motivo no puedo editar el comentario.
| # tr.pretty_print() | ||
|
|
||
| def main(): | ||
| global filename |
There was a problem hiding this comment.
Ver comentario en línea 8.
| global filename |
| Definition 3: A lexicon is a finite set of lexical items. | ||
| """ | ||
| def __init__(self): | ||
| def __init__(self,file_name=None): |
There was a problem hiding this comment.
Si querés que por defecto file_name sea igual a "lexicon.xml", podés poner directamente:
| def __init__(self,file_name=None): | |
| def __init__(self,file_name="lexicon.xml"): |
De esta forma, si el usuario indica otro file_name, se va a tomar el del usuario. Y, si no, se toma "lexicon.xml". Con esto podés borrar las líneas 17-20.
There was a problem hiding this comment.
Hola Macarena. Entiendo que sería más prolijo eliminar la variable filename, y que hay un global filename innecesario. Lo que no me queda claro es: al hacer este cambio, si el usuario no instancia directamente el Lexicon, ¿cómo puede saber la función manual_derivation, sin una variable que contenga el nombre del archivo, si pasarle o no un argumento a Lexicon()? El código actual también usa esa variable para mostrar en pantalla el nombre del archivo en uso.
Sí se me ocurre que podría conservar esta variable, sólo que con valor None. Y luego un if isinstance(filename, type(None)): etc.
Perfecto el resto de los comentarios.
Gracias!
There was a problem hiding this comment.
Hola, Facundo. Estoy un poco desorientada. ¿Puede ser que esta respuesta sea para el comentario donde te sugiero borrar las variables con global? La función manual_derivation está definida en el archivo main.py. Ahí, en la línea 5 ya estás definiendo la variable filename y asinándole un valor (lexicon.xml). En la línea 26 de ese mismo archivo se instancia la clase Lexicon con ese filename. No sé si con esto respondo a tu comentario o iba por otro lado. Avisame sin problemas y lo podemos seguir conversando.
There was a problem hiding this comment.
Mi comentario era una duda sobre la "plausibilidad" de modificar el constructor de la clase Lexicon y eliminar la variable filename de main.py (pero tal vez entendí mal la propuesta). Y decía, en ese caso, ¿cómo va a saber la función manual_derivation si pasarle un parámetro o no a la clase (y que tome el nombre del archivo por defecto)?
En cuanto a las dos líneas global filename, veo bien que la que se encuentra en la función print_grammars() no tiene mucho sentido. Pero en la función main() la agregué con la idea de poder modificar el valor de la variable desde dentro de la función (siguiendo el último ejemplo de esta explicación). Y a su vez está definida de manera global (en la línea 5) para que su valor pueda ser accesible para print_grammars(), aunque acá la redefinición como global sea innecesaria.
Comprendido lo de las dos clases Derivation que comentabas más abajo.
Gracias!
There was a problem hiding this comment.
Hola, Facundo. Claro. Creo que algunas cosas me quedan más claras ahora.
Respecto de main.py, es cierto que si no lo hacés con global, no podés modificar la variable que está definida afuera. No me había dado cuenta. Disculpame. Una alternativa para todo eso podría ser cambiar la línea 8 por filename="lexicon.xml" (dentro de la función main()) y que la función print_grammars() tome ese valor. Te sugiero eso porque eso te permitiría, si quisieras, definir la función print_grammars() en otro archivo si quisieras (para que no te quede un main.py con tanta lógica) y, por otro lado, evitar el extraño diseño al tener una función que toma una variable definida externamente mientras que otras funciones necesitan tomar ese valor como un parámetro. En caso de que prefirieses seguir con el global, por ahí consideraría moverlo al flujo dentro del elif para el valor 3, dado que solo ahí la función main() cambia su valor. Antes no. Esto último, asumiento que dejás la variable definida también en la línea 5 tal como esta.
En cuando a la definición de la clase Lexicon, no me termina de quedar clara tu duda. La sugerencia que te hago es para evitar el flujo de si file_name es None. Cuando se quiere definir una variable con un valor por defecto, se suele hacer como te sugiero. La función manual_derivation() debería pasarle el valor del filename de todos modos porque, si el usuario elige uno que no es el que se indica por defecto, está bien pasarlo. Y si es el por defecto, no está mal tampoco. No termino de entender la lógica externa con el None que planteas (dentro de manual_derivation() si no entiendo mal).
Si querés, podemos arreglar para hablarlo por videollamada dos minutos porque quizá es que no estoy atando todos los cabos correctamente.
There was a problem hiding this comment.
Hola Macarena! Perfecto, parece mucho más sensato definir la variable filename dentro de main() y pasársela a print_grammars(). Así además print_grammars() queda como una función más flexible (se le podrían sumar otros parámetros).
También creo que entendí lo de file_name. Eso me permite ahorrarme el bloque dentro de la definición de la clase:
if file_name == None:
file_name = "lexicon.xml"
else:
file_name = file_name
Que la verdad no tenía mucho sentido porque los valores por default se definen mejor como vos decís.
¿Te referías a esto? Si no, hacemos la videollamada y lo terminamos de resolver.
Gracias!
There was a problem hiding this comment.
Hola, Facundo. Disculpame la demora en la repsuesta. La notificación quedó sepultada entre otros correos y no la había visto. Efectiamente e refería a esa porción de código. Al indicarla por defecto y/o pasarla siempre desde la función que llama a la clase, te evitás el condicional.
| __all__ = ["Derivation","parse","phrase_to_list","word_to_lex"] | ||
|
|
||
| from .derivation import * No newline at end of file | ||
| from .derivation import * |
There was a problem hiding this comment.
Tanto derivation como parser tiene una clase llamada Derivation. Deberías llamarlas de manera distinta para saber cuál estás incluyendo en el __all__.
PS: O, más bien, de ser posible, podrías modificar la clase Derivation original y agregarle la funcionalidad que desarrollaste.
There was a problem hiding this comment.
Mi idea acá era extender la clase Derivation en el módulo parser, para no tocar el archivo original (cosa que igual terminé haciendo). Por eso al "redefinirla", ponía: class Derivation(derivation.Derivation):. Pero puede que la haya duplicado sin querer, la verdad es que no encontré información muy clara sobre esto. Gracias
There was a problem hiding this comment.
Claro. Acá hay varias cosas. Voy por partes y lo podemos ir charlando de todas formas:
- ¿Por qué no querías tocar el archivo original? Si vos estás de acuerdo con que exista una clase llamada
Derivationy que esa clase tenga determinados métodos y atributos, lo lógico sería que, al ir desarrollando nueva funcionalidad (lo que vos hiciste), vayas expandiendo los métodos de esa clase, pero no duplicándola. - Al definir una nueva clase
Derivationque hereda de la primera clase, estás generando una nueva clase que tiene todos los atributos de la primera más los que vos sumaste. En tanto estás usando el mismo nombre es casi como si la redefinieras. Pero no es exactamente eso. O sea, si yo importo la claseDerivationdederivation.pyvoy a tener la clase original sin ampliar. Si la importo desdeparser.pyvoy a tener la clase ampliada. La pregunta es ¿por qué no tener una única clase que tenga todo? Y, si te fuera de interés que el usuario pueda distinguir entre la clase amppliada y la original, consideraría cambiarle el nombre. De todos modos, recordá que las definiciones de clases también tienen que tener cierto sentido lógico desde la atribución de responsabilidades. Si tenés dudas de esto, decime. Por ahí podemos charlar 15 minutos y queda más claro. - Si bien sería un poco raro, podría ser que tengas dos clases llamadas
Derivationen archivos distintos de tu código. El problema acá es que vos estás importando todo lo que hay en el archivoparser.pyy todo lo que hay en el archivoderivation.py. Y después, en__all__le indicás que seleccioneDerivation: ¿a qué claseDerivationte referís? ¿a la que está en el primero o en el segundo archivo?
| self.lexical_item = lexical_item | ||
| self.triggers = { f for f in self.lexical_item.syn if type(f) is Trigger_Feature } | ||
| cat_features = { f for f in self.lexical_item.syn if type(f) is Cat_Feature } | ||
| import copy |
There was a problem hiding this comment.
Consideraría cambiar esta línea al principio del archivo. Creo que en el código original también hay algún import dentro de funciones, pero en general los imports se organizan al principio del archivo, salvo excepciones como que tengas un flujo de __main__.
| return word_list | ||
|
|
||
| def get_possible_lexicons(lexicon): | ||
| import itertools |
There was a problem hiding this comment.
Consideraría mover este import al inicio del archivo.
No description provided.