{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Resumen de NLTK: Análisis sintáctico\n", "\n", "Este resumen se corresponde con el capítulo 8 del NLTK Book [Analyzing Sentence Structure](http://www.nltk.org/book/ch08.html). La lectura del capítulo es muy recomendable.\n", "\n", "\n", "En este resumen vamos a repasar cómo crear gramáticas con NLTK y cómo crear herramientas que nos permitan analizar sintácticamente oraciones sencillas.\n", "\n", "Para empezar, necesitamos importar el módulo `nltk` que nos da acceso a todas las funcionalidades:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import nltk" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Gramáticas Independientes del Contexto (CFG)\n", "\n", "Noam Chmosky definió una [jerarquía de lenguajes y gramáticas](http://es.wikipedia.org/wiki/Jerarqu%C3%ADa_de_Chomsky) que se utiliza habitualmente en Lingüística e Informática para clasificar lenguajes y gramáticas formales. Cuando queremos modelar fenómenos lingüísticos de las lenguas naturales, el tipo de gramática más adeacuado es el conocido como Tipo 2 o [Gramáticas Independientes del Contexto](http://es.wikipedia.org/wiki/Gram%C3%A1tica_libre_de_contexto) o *Context-Free Grammars (CFG)* en inglés.\n", "\n", "Vamos a definir una gramática simplemente como un conjunto de reglas de reescritura o transformación. Sin entrar en muchos detalles sobre las restricciones que tienen que cumplir las reglas de las gramáticas de Tipo 2, es importante que tengamos en cuenta lo siguiente:\n", "\n", "- Las gramáticas formales manejan dos tipos de alfabetos.\n", " 1. Los **símbolos no terminales** son los componentes intermedios que utilizamos en las reglas. Todo símbolo no terminal tiene que ser definido como una secuenca de otros símbolos. En nuestro caso, los no terminales van a ser las categorías sintácticas. \n", " 2. Los **símbolos terminales** son los componentes finales reconocidos por la gramática. En nuestro caso, los terminales van a ser las palabras de las oraciones que queremos analizar sintácticamente.\n", "- Todas las reglas de una gramática formal tienen la forma `Símbolo1 -> Símbolo2, Símbolo3... SímboloN` y se leen como *el `Símbolo1` se define/está formado/se reescribe como una secuencia formada por `Símbolo2`, `Símbolo3`, etc.*\n", "- En las gramáticas independientes del contexto, la parte situada a la izquierda de la flecha `->` es siempre un único símbolo no terminal.\n", "\n", "## Gramáticas Generativas en NLTK\n", "\n", "Pues bien, para definir nuestras gramáticas en NLTK podemos escribirlas en un fichero aparte o como una cadena de texto siguiendo el formalismo de las gramaticas generativas de Chomsky. Vamos a definir una sencilla gramática capaz de reconocer la famosa frase de los hermanos Marx *I shot an elephant in my pajamas*, y la vamos a guardar como una cadena de texto en la variable `g1`." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "g1 = \"\"\"\n", "S -> NP VP\n", "NP -> Det N | Det N PP | 'I'\n", "VP -> V NP | VP PP\n", "PP -> P NP\n", "Det -> 'an' | 'my'\n", "N -> 'elephant' | 'pajamas'\n", "V -> 'shot'\n", "P -> 'in'\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fíjate cómo hemos definido nuestra gramática:\n", "\n", "- Hemos encerrado todo entre triples comillas dobles. Recuerda que esta sintaxis de Python permite crear cadenas que contengan retornos de carro y ocupen más de una línea de longitud.\n", "- Para los no terminales utilizamos las convenciones habituales para las estructuras sintácticas y las categorías de palabras y los escribimos en mayúsculas. Las etiquetas son autoexplicativas, aunque estén en inglés.\n", "- Lo no terminales van escritos entre comillas simples.\n", "- Cuando un no terminal se puede definir de más de una forma, marcamos la disyunción con la barra vertical `|`. \n", "- Tenemos reglas que se interpretan de la siguiente manera: *una oración se define como una sintagma nominal y un sintagma verbal*; *un sintagma nominal se define como un determinante y un nombre, o un determinante, un nombre y un sintagma preposicional, o la palabra I*, etc.\n", "\n", "A partir de nuestra gramática en una cadena de texto, necesitamos crear un analizador que podamos utilizar posterioremente. Para ello, es imprescindible parsearla antes con el método `nltk.CFG.fromstring()`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "grammar1 = nltk.CFG.fromstring(g1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Con el objeto `grammar1` ya creado, creamos el analizador con el método `nltk.ChatParser`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "analyzer = nltk.ChartParser(grammar1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Una vez creado nuestro analizador ya lo podemos utilizar. Tenemos a nuestro alcance el método `.parse` para analizar sintácticamente cualquier oración que se especifique como una cadena de palabras. Nuestra gramática es bastante limitada, pero podemos utilizarla para analizar la oración *I shot an elephant in my pajamas*. Si imprimimos el resultado del método, obtenemos el árbol sintáctico." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(S\n", " (NP I)\n", " (VP\n", " (VP (V shot) (NP (Det an) (N elephant)))\n", " (PP (P in) (NP (Det my) (N pajamas)))))\n", "(S\n", " (NP I)\n", " (VP\n", " (V shot)\n", " (NP (Det an) (N elephant) (PP (P in) (NP (Det my) (N pajamas))))))\n" ] } ], "source": [ "oracion = \"I shot an elephant in my pajamas\".split()\n", "# guardamos todos los posibles análisis sintácticos en trees\n", "trees = analyzer.parse(oracion)\n", "for tree in trees:\n", " print(tree)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por si no te has dado cuenta, la oración *I shot an elephant in my pajamas* es ambigua en inglés: se trata del típico ejemplo de *PP attachment* (saber exactamente a qué nodo está modificando un sintagma preposicional). Existe una doble interpretación para el sintagma preposicional *in my pajamas*: En el momento del disparo, ¿quién llevaba puesto el pijama? ¿El elefante o yo? Pues bien, nuestra gramática recoge esta ambigüedad y sería capaz de analizarla de dos maneras diferentes, tal y como se muestra en la celda anterior. \n", "\n", "En el caso de que nos interese solo generar uno de los posibles análisis, podemos utilizar el método `parse_one()`, como se muestra a continuación." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(S\n", " (NP I)\n", " (VP\n", " (VP (V shot) (NP (Det an) (N elephant)))\n", " (PP (P in) (NP (Det my) (N pajamas)))))\n" ] } ], "source": [ "print(analyzer.parse_one(oracion))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recuerda que para imprimir el árbol sintáctico hay que iterar (con un bucle `for`, por ejemplo) sobre el objeto que devuelve el método `parse()` y utilizar la función `print`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "print(analyzer.parse(oracion))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A continuación modifico ligeramente mi gramática `g1` para incluir una nueva categoría gramatical `PRO` y añadir algo de volcabulario nuevo. Compara ambos ejemplos:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(S\n", " (NP (PRO I))\n", " (VP\n", " (VP (V shot) (NP (Det an) (N elephant)))\n", " (PP (P in) (NP (Det my) (N pajamas)))))\n", "(S\n", " (NP (PRO I))\n", " (VP\n", " (V shot)\n", " (NP (Det an) (N elephant) (PP (P in) (NP (Det my) (N pajamas))))))\n", "\n", " ------------------------------- \n", "\n", "(S (NP (PRO you)) (VP (V shot) (NP (Det my) (N elephant))))\n" ] } ], "source": [ "g1v2 = \"\"\"\n", "S -> NP VP\n", "NP -> Det N | Det N PP | PRO\n", "VP -> V NP | VP PP\n", "PP -> P NP\n", "Det -> 'an' | 'my'\n", "PRO -> 'I' | 'you'\n", "N -> 'elephant' | 'pajamas'\n", "V -> 'shot'\n", "P -> 'in'\n", "\"\"\"\n", "\n", "grammar1v2 = nltk.CFG.fromstring(g1v2)\n", "analyzer1v2 = nltk.ChartParser(grammar1v2)\n", "\n", "# itero sobre la estructura que devuelve parse()\n", "for tree in analyzer1v2.parse(oracion):\n", " print(tree)\n", " \n", "print(\"\\n\", \"-------------------------------\", \"\\n\")\n", "for tree in analyzer1v2.parse(\"you shot my elephant\".split()):\n", " print(tree)\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### NOTA IMPORTANTE sobre errores y el comportamiento de `parse()`\n", "\n", "Cuando un analizador reconoce todo el vocabulario de una oración de entrada pero es incapaz de analizarla, el método `parse()` no da error pero devuelve un objeto vacío. En este caso, la oración es agramatical según nuestra gramática.\n" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for tree in analyzer.parse(\"shot an pajamas elephant my I\".split()):\n", " print(\"El análisis sintáctico es el siguiente\")\n", " print(tree)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sin embargo, cuando el analizador no reconoce todo el vocabulario (porque utilizamos una palabra no definida dentro del léxico), el método `parse()` falla y muestra un mensaje de error de tipo `ValueError` como el siguiente. Fíjate solo en la última línea:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "ename": "ValueError", "evalue": "Grammar does not cover some of the input words: \"'our', 'time', 'is', 'running', 'out'\".", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mtree\u001b[0m \u001b[1;32min\u001b[0m \u001b[0manalyzer\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mparse\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"our time is running out\"\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msplit\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"El análisis sintáctico es el siguiente\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtree\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m/usr/local/lib/python3.4/dist-packages/nltk/parse/chart.py\u001b[0m in \u001b[0;36mparse\u001b[1;34m(self, tokens, tree_class)\u001b[0m\n\u001b[0;32m 1348\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1349\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mparse\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtokens\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtree_class\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mTree\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1350\u001b[1;33m \u001b[0mchart\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mchart_parse\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtokens\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1351\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0miter\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mchart\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mparses\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_grammar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtree_class\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mtree_class\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1352\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m/usr/local/lib/python3.4/dist-packages/nltk/parse/chart.py\u001b[0m in \u001b[0;36mchart_parse\u001b[1;34m(self, tokens, trace)\u001b[0m\n\u001b[0;32m 1307\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1308\u001b[0m \u001b[0mtokens\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtokens\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1309\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_grammar\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcheck_coverage\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtokens\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1310\u001b[0m \u001b[0mchart\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_chart_class\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtokens\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1311\u001b[0m \u001b[0mgrammar\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_grammar\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m/usr/local/lib/python3.4/dist-packages/nltk/grammar.py\u001b[0m in \u001b[0;36mcheck_coverage\u001b[1;34m(self, tokens)\u001b[0m\n\u001b[0;32m 629\u001b[0m \u001b[0mmissing\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m', '\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'%r'\u001b[0m \u001b[1;33m%\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mw\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mw\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mmissing\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 630\u001b[0m raise ValueError(\"Grammar does not cover some of the \"\n\u001b[1;32m--> 631\u001b[1;33m \"input words: %r.\" % missing)\n\u001b[0m\u001b[0;32m 632\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 633\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m_calculate_grammar_forms\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mValueError\u001b[0m: Grammar does not cover some of the input words: \"'our', 'time', 'is', 'running', 'out'\"." ] } ], "source": [ "for tree in analyzer.parse(\"our time is running out\".split()):\n", " print(\"El análisis sintáctico es el siguiente\")\n", " print(tree)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Tenlo en cuenta a la hora de detectar errores en tu código. \n", "\n", "\n", "## Gramáticas en español\n", "\n", "Visto un primer ejemplo de CFG, vamos a cambiar de lengua y crear un analizador para oraciones sencillas en español. El procedimiento es el mismo, definimos nuestra gramática en formato de Chomsky en un fichero aparte o en una cadena de texto, la parseamos con el método `nltk.CFG.fromstring()` y creamos un analizador con el método `nltk.ChartParser()`:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "g2 = \"\"\"\n", "O -> SN SV\n", "SN -> Det N | Det N Adj | Det Adj N | NProp | SN SP\n", "SV -> V | V SN | V SP | V SN SP\n", "SP -> Prep SN\n", "Det -> 'el' | 'la' | 'un' | 'una' \n", "N -> 'niño' | 'niña' | 'manzana' | 'pera' | 'cuchillo'\n", "NProp -> 'Juan' | 'Ana' | 'Perico' \n", "Adj -> 'bonito' | 'pequeña' | 'verde'\n", "V -> 'come' | 'salta' | 'pela' | 'persigue'\n", "Prep -> 'de' | 'con' | 'desde' | 'a'\n", "\"\"\"\n", "\n", "grammar2 = nltk.CFG.fromstring(g2)\n", "analizador2 = nltk.ChartParser(grammar2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos a probar si es capaz de analizar distintas oraciones es español. Para hacerlo más divertido, vamos a guardar varias oraciones separadas por un intro (simbolizado por el metacarácter `\\n`) en una lista de cadenas llamda `oraciones`. Iteramos sobre esas oraciones, las imprimimos, después las rompemos en listas de palabras (con el método `.split()`) e imprimimos el resultado de analizarlas con nuestro analizador.\n" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ana salta\n", "(O (SN (NProp Ana)) (SV (V salta))) \n", "\n", "la niña pela una manzana verde con el cuchillo\n", "(O\n", " (SN (Det la) (N niña))\n", " (SV\n", " (V pela)\n", " (SN (Det una) (N manzana) (Adj verde))\n", " (SP (Prep con) (SN (Det el) (N cuchillo))))) \n", "\n", "(O\n", " (SN (Det la) (N niña))\n", " (SV\n", " (V pela)\n", " (SN\n", " (SN (Det una) (N manzana) (Adj verde))\n", " (SP (Prep con) (SN (Det el) (N cuchillo)))))) \n", "\n", "Juan come un cuchillo bonito desde el niño\n", "(O\n", " (SN (NProp Juan))\n", " (SV\n", " (V come)\n", " (SN (Det un) (N cuchillo) (Adj bonito))\n", " (SP (Prep desde) (SN (Det el) (N niño))))) \n", "\n", "(O\n", " (SN (NProp Juan))\n", " (SV\n", " (V come)\n", " (SN\n", " (SN (Det un) (N cuchillo) (Adj bonito))\n", " (SP (Prep desde) (SN (Det el) (N niño)))))) \n", "\n", "un manzana bonito salta el cuchillo desde el niño verde\n", "(O\n", " (SN (Det un) (N manzana) (Adj bonito))\n", " (SV\n", " (V salta)\n", " (SN (Det el) (N cuchillo))\n", " (SP (Prep desde) (SN (Det el) (N niño) (Adj verde))))) \n", "\n", "(O\n", " (SN (Det un) (N manzana) (Adj bonito))\n", " (SV\n", " (V salta)\n", " (SN\n", " (SN (Det el) (N cuchillo))\n", " (SP (Prep desde) (SN (Det el) (N niño) (Adj verde)))))) \n", "\n", "el cuchillo verde persigue a la pequeña manzana de Ana\n", "(O\n", " (SN (Det el) (N cuchillo) (Adj verde))\n", " (SV\n", " (V persigue)\n", " (SP\n", " (Prep a)\n", " (SN\n", " (SN (Det la) (Adj pequeña) (N manzana))\n", " (SP (Prep de) (SN (NProp Ana))))))) \n", "\n", "el cuchillo verde persigue a Ana\n", "(O\n", " (SN (Det el) (N cuchillo) (Adj verde))\n", " (SV (V persigue) (SP (Prep a) (SN (NProp Ana))))) \n", "\n" ] } ], "source": [ "oraciones = \"\"\"Ana salta\n", "la niña pela una manzana verde con el cuchillo\n", "Juan come un cuchillo bonito desde el niño\n", "un manzana bonito salta el cuchillo desde el niño verde\n", "el cuchillo verde persigue a la pequeña manzana de Ana\n", "el cuchillo verde persigue a Ana\"\"\".split(\"\\n\")\n", "\n", "for oracion in oraciones:\n", " print(oracion)\n", " for tree in analizador2.parse(oracion.split()):\n", " print(tree, \"\\n\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos a aumentar la cobertura de nuestra gramática de modo que sea capaz de reconocer y analizar oraciones coordinadas. Para ello, modificamos la regla en la que definimos la oración añadiendo una definición recursivaque defina oración como la secuencia de una oración (`O`) seguida de una conjunción (`Conj`) y de otra oración (`O`). Por último añadimos también algo de léxico nuevo: un par de conjunciones." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "g3 = \"\"\"\n", "O -> SN SV | O Conj O\n", "SN -> Det N | Det N Adj | Det Adj N | NProp | SN SP\n", "SV -> V | V SN | V SP | V SN SP\n", "SP -> Prep SN\n", "Det -> 'el' | 'la' | 'un' | 'una' \n", "N -> 'niño' | 'niña' | 'manzana' | 'pera' | 'cuchillo'\n", "NProp -> 'Juan' | 'Ana' | 'Perico' \n", "Adj -> 'bonito' | 'pequeña' | 'verde'\n", "V -> 'come' | 'salta' | 'pela' | 'persigue'\n", "Prep -> 'de' | 'con' | 'desde' | 'a'\n", "Conj -> 'y' | 'pero'\n", "\"\"\"\n", "\n", "# Ahora fijate cómo creamos en analizador en un solo paso\n", "# compáralo con los ejemplos anteriores\n", "analizador3 = nltk.ChartParser(nltk.CFG.fromstring(g3))" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(O\n", " (O\n", " (O (SN (Det la) (N manzana)) (SV (V salta)))\n", " (Conj y)\n", " (O (SN (Det el) (N niño)) (SV (V come))))\n", " (Conj pero)\n", " (O\n", " (SN (Det el) (N cuchillo) (Adj verde))\n", " (SV\n", " (V persigue)\n", " (SP\n", " (Prep a)\n", " (SN\n", " (SN (Det la) (Adj pequeña) (N manzana))\n", " (SP (Prep de) (SN (NProp Ana))))))))\n", "(O\n", " (O (SN (Det la) (N manzana)) (SV (V salta)))\n", " (Conj y)\n", " (O\n", " (O (SN (Det el) (N niño)) (SV (V come)))\n", " (Conj pero)\n", " (O\n", " (SN (Det el) (N cuchillo) (Adj verde))\n", " (SV\n", " (V persigue)\n", " (SP\n", " (Prep a)\n", " (SN\n", " (SN (Det la) (Adj pequeña) (N manzana))\n", " (SP (Prep de) (SN (NProp Ana)))))))))\n" ] } ], "source": [ "for tree in analizador3.parse(\"\"\"la manzana salta y el niño come pero el cuchillo \n", "verde persigue a la pequeña manzana de Ana\"\"\".split()):\n", " print(tree)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recuerda que una gramática no es un programa: es una simple descripción que permite establecer qué estructuras sintácticas están bien formadas (la oraciones gramaticales) y cuáles no (las oraciones agramaticales). Cuando una oración es reconocida por una gramática (y en consecuencia, está bien formada), el analizador puede representar la estructura en forma de árbol.\n", "\n", "NLTK proporciona acceso a distintos tipo de analizadores (árboles de dependencias, gramáticas probabilísticas, etc), aunque nosotros solo hemos utilizado el más sencillo de ellos: `nltk.ChartParser()`. Estos analizadores sí son programitas que permiten leer una gramática y analizar las oraciones que proporcionemos como entrada del método `parse()`.\n", "\n", "## Otro ejemplo\n", "\n", "En clase improvisamos un poco y proponemos el siguiente ejemplo de gramática. Vamos a ir complicándola de manera incremental. Comencemos con unas cuantas oraciones de ejemplo. " ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# ojo, son sencillas, pero contienen oraciones impersonales, verbos copulativos, sujetos elípticos\n", "oraciones = \"\"\"mañana es viernes\n", "hoy es jueves\n", "tenéis sueño\n", "hace frío\n", "Pepe hace sueño\"\"\".split(\"\\n\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# escribe tu gramática en esta celda\n", "g4 = \"\"\"\n", "\"\"\"\n", "\n", "analyzer4 = nltk.ChartParser(nltk.CFG.fromtring(g4))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# ¿qué tal funciona?\n", "for oracion in oraciones:\n", " print(oracion)\n", " for tree in analyzer4.parse(oracion.split()):\n", " print(tree, \"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "¿Podemos extender `g4` para que reconozca oraciones subordinadas introducidas con *verbos de lengua* o *de pensamiento*. Me refiero a oraciones del tipo: *Pepe cree que mañana es viernes*, *María dice que Pepe cree que mañana es viernes*, etc.\n", "\n", "Aumenta tu vocabulario añadiendo tantos terminales como te haga falta." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "oraciones = \"\"\"Pepe cree que mañana es viernes\n", "María dice que Pepe cree que mañana es viernes\"\"\".split()\n", "\n", "# escribe la extensión de tu gramática en esta celda\n", "g5 = \"\"\"\n", "\"\"\"\n", "\n", "analyzer5 = nltk.ChartParser(nltk.CFG.fromstring(g5))\n", "\n", "# ¿qué tal funciona?\n", "for oracion in oraciones:\n", " print(oracion)\n", " for tree in analyzer5.parse(oracion.split()):\n", " print(tree, \"\\n\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.4.3+" } }, "nbformat": 4, "nbformat_minor": 0 }