{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Resumen NLTK: Acceso a corpus de texto y recursos léxicos\n", "\n", "Este resumen se corresponde con el capítulo 2 del NLTK Book [Accessing Text Corpora and Lexical Resources](http://www.nltk.org/book/ch02.html). La lectura del capítulo es muy recomendable.\n", "\n", "\n", "## Corpus no anotados: el Proyecto Gutenberg\n", "\n", "NLTK no da acceso directo a varias colecciones de textos. Para empezar, vamos a juguetear un poco con los libros del [Proyecto Gutenberg](http://www.gutenberg.org), un repositorio público de libros libres y/o sin derechos de copyright en vigor.\n", "Antes de nada, necesitamos importar el módulo `gutenberg` que está en la librería `nltk.corpus`." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from nltk.corpus import gutenberg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos listar el catálogo de libros del Proyecto Gutenberg disponibles desde NLTK a través del método `nltk.corpus.gutenberg.fileids`" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt', 'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']\n" ] } ], "source": [ "print(gutenberg.fileids())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para cargar alguno de estos libros en variables y poder manipularlos directamente, podemos utilizar varios métodos.\n", "\n", "- `gutenberg.raw` recupera el texto como una única cadena de caracteres.\n", "- `gutenberg.words` recupera el texto tokenizado en palabras. El método devuelve una lista palabras.\n", "- `gutenberg.sents` recupera el texto segmentado por oraciones. El método devuelve una lista de oraciones. Cada oración es a su vez una lista de palabras.\n", "- `gutenberg.paras` recupera el texto segmentado por párrafos. El método devuelve una lista de párrafos. Cada párrafo es una lista de oraciones, cada oración es a su vez una lista de palabras." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Alice's Adventures in Wonderland by Lewis Carroll 1865]\n", "\n", "CHAPTER I. Down the Rabbit-Hole\n", "\n", "Alice was beginning to get very tired of sitting by her sister on the\n", "bank, and of having nothing to do: once\n", "[The King James Bible]\n", "\n", "The Old Testament of the King James Bible\n", "\n", "The First Book of Moses: Called Genesis\n", "\n", "\n", "1:1 In the beginning God created the heaven and the earth.\n", "\n", "1:2 And the earth was without \n" ] } ], "source": [ "# cargo la vesión 'cruda' de un par de libros. Como son libros del Proyecto Gutenberg, se trata de ficheros en texto plano\n", "alice = gutenberg.raw(\"carroll-alice.txt\")\n", "print(alice[:200]) # imprimo los primeros 200 caracteres del libro de Alicia\n", "\n", "bible = gutenberg.raw(\"bible-kjv.txt\")\n", "print(bible[:200]) # imprimo los primeros 200 caracteres de la Biblia" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "821133 1010654\n", "['[The', 'King', 'James', 'Bible]', 'The', 'Old', 'Testament', 'of', 'the', 'King', 'James', 'Bible', 'The', 'First', 'Book', 'of', 'Moses:', 'Called', 'Genesis', '1:1', 'In', 'the', 'beginning', 'God', 'created', 'the', 'heaven', 'and', 'the', 'earth.', '1:2', 'And', 'the', 'earth', 'was', 'without', 'form,', 'and', 'void;', 'and', 'darkness', 'was', 'upon', 'the', 'face', 'of', 'the', 'deep.', 'And', 'the', 'Spirit', 'of', 'God', 'moved', 'upon', 'the', 'face', 'of', 'the', 'waters.', '1:3', 'And', 'God', 'said,', 'Let', 'there', 'be', 'light:', 'and', 'there', 'was', 'light.', '1:4', 'And', 'God', 'saw', 'the', 'light,', 'that', 'it', 'was', 'good:', 'and', 'God', 'divided', 'the', 'light', 'from', 'the', 'darkness.', '1:5', 'And', 'God', 'called', 'the', 'light', 'Day,', 'and', 'the', 'darkness']\n", "-----------------------------------------------\n", "['[', 'The', 'King', 'James', 'Bible', ']', 'The', 'Old', 'Testament', 'of', 'the', 'King', 'James', 'Bible', 'The', 'First', 'Book', 'of', 'Moses', ':', 'Called', 'Genesis', '1', ':', '1', 'In', 'the', 'beginning', 'God', 'created', 'the', 'heaven', 'and', 'the', 'earth', '.', '1', ':', '2', 'And', 'the', 'earth', 'was', 'without', 'form', ',', 'and', 'void', ';', 'and', 'darkness', 'was', 'upon', 'the', 'face', 'of', 'the', 'deep', '.', 'And', 'the', 'Spirit', 'of', 'God', 'moved', 'upon', 'the', 'face', 'of', 'the', 'waters', '.', '1', ':', '3', 'And', 'God', 'said', ',', 'Let', 'there', 'be', 'light', ':', 'and', 'there', 'was', 'light', '.', '1', ':', '4', 'And', 'God', 'saw', 'the', 'light', ',', 'that', 'it']\n" ] } ], "source": [ "# segmentamos el texto en palabras teniendo en cuenta los espacios \n", "bible_tokens = bible.split()\n", "# cargamos la versión de la Biblia segmentado en palabras\n", "bible_words = gutenberg.words(\"bible-kjv.txt\")\n", "\n", "# no da el mismo número de tokens\n", "print(len(bible_tokens), len(bible_words))\n", "\n", "print(bible_tokens[:100])\n", "print(\"-----------------------------------------------\")\n", "print(bible_words[:100])" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['[', 'Alice', \"'\", 's', 'Adventures', 'in', 'Wonderland', 'by', 'Lewis', 'Carroll', '1865', ']', 'CHAPTER', 'I', '.', 'Down', 'the', 'Rabbit', '-', 'Hole']\n", "34110\n" ] } ], "source": [ "# cargo la versión de Alicia segmentada en palabras\n", "alice_words = gutenberg.words(\"carroll-alice.txt\")\n", "print(alice_words[:20]) # imprimo las primeros 20 palabras\n", "print(len(alice_words))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Alice tiene 1703 oraciones\n", "[['Down', 'the', 'Rabbit', '-', 'Hole'], ['Alice', 'was', 'beginning', 'to', 'get', 'very', 'tired', 'of', 'sitting', 'by', 'her', 'sister', 'on', 'the', 'bank', ',', 'and', 'of', 'having', 'nothing', 'to', 'do', ':', 'once', 'or', 'twice', 'she', 'had', 'peeped', 'into', 'the', 'book', 'her', 'sister', 'was', 'reading', ',', 'but', 'it', 'had', 'no', 'pictures', 'or', 'conversations', 'in', 'it', ',', \"'\", 'and', 'what', 'is', 'the', 'use', 'of', 'a', 'book', \",'\", 'thought', 'Alice', \"'\", 'without', 'pictures', 'or', 'conversation', \"?'\"], ['So', 'she', 'was', 'considering', 'in', 'her', 'own', 'mind', '(', 'as', 'well', 'as', 'she', 'could', ',', 'for', 'the', 'hot', 'day', 'made', 'her', 'feel', 'very', 'sleepy', 'and', 'stupid', '),', 'whether', 'the', 'pleasure', 'of', 'making', 'a', 'daisy', '-', 'chain', 'would', 'be', 'worth', 'the', 'trouble', 'of', 'getting', 'up', 'and', 'picking', 'the', 'daisies', ',', 'when', 'suddenly', 'a', 'White', 'Rabbit', 'with', 'pink', 'eyes', 'ran', 'close', 'by', 'her', '.']]\n" ] } ], "source": [ "# cargo la versión de Alicia segmentada en oraciones\n", "alice_sents = gutenberg.sents(\"carroll-alice.txt\")\n", "print(\"Alice tiene\", len(alice_sents), \"oraciones\")\n", "print(alice_sents[2:5]) # imprimo la tercera, cuarta y quinta oración" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Alice tiene 817 párrafos\n", "[['[', 'Alice', \"'\", 's', 'Adventures', 'in', 'Wonderland', 'by', 'Lewis', 'Carroll', '1865', ']']]\n", "-------------------\n", "[['CHAPTER', 'I', '.'], ['Down', 'the', 'Rabbit', '-', 'Hole']]\n", "-------------------\n", "[['Alice', 'was', 'beginning', 'to', 'get', 'very', 'tired', 'of', 'sitting', 'by', 'her', 'sister', 'on', 'the', 'bank', ',', 'and', 'of', 'having', 'nothing', 'to', 'do', ':', 'once', 'or', 'twice', 'she', 'had', 'peeped', 'into', 'the', 'book', 'her', 'sister', 'was', 'reading', ',', 'but', 'it', 'had', 'no', 'pictures', 'or', 'conversations', 'in', 'it', ',', \"'\", 'and', 'what', 'is', 'the', 'use', 'of', 'a', 'book', \",'\", 'thought', 'Alice', \"'\", 'without', 'pictures', 'or', 'conversation', \"?'\"]]\n", "-------------------\n", "[['So', 'she', 'was', 'considering', 'in', 'her', 'own', 'mind', '(', 'as', 'well', 'as', 'she', 'could', ',', 'for', 'the', 'hot', 'day', 'made', 'her', 'feel', 'very', 'sleepy', 'and', 'stupid', '),', 'whether', 'the', 'pleasure', 'of', 'making', 'a', 'daisy', '-', 'chain', 'would', 'be', 'worth', 'the', 'trouble', 'of', 'getting', 'up', 'and', 'picking', 'the', 'daisies', ',', 'when', 'suddenly', 'a', 'White', 'Rabbit', 'with', 'pink', 'eyes', 'ran', 'close', 'by', 'her', '.']]\n", "-------------------\n", "[['There', 'was', 'nothing', 'so', 'VERY', 'remarkable', 'in', 'that', ';', 'nor', 'did', 'Alice', 'think', 'it', 'so', 'VERY', 'much', 'out', 'of', 'the', 'way', 'to', 'hear', 'the', 'Rabbit', 'say', 'to', 'itself', ',', \"'\", 'Oh', 'dear', '!'], ['Oh', 'dear', '!'], ['I', 'shall', 'be', 'late', \"!'\"], ['(', 'when', 'she', 'thought', 'it', 'over', 'afterwards', ',', 'it', 'occurred', 'to', 'her', 'that', 'she', 'ought', 'to', 'have', 'wondered', 'at', 'this', ',', 'but', 'at', 'the', 'time', 'it', 'all', 'seemed', 'quite', 'natural', ');', 'but', 'when', 'the', 'Rabbit', 'actually', 'TOOK', 'A', 'WATCH', 'OUT', 'OF', 'ITS', 'WAISTCOAT', '-', 'POCKET', ',', 'and', 'looked', 'at', 'it', ',', 'and', 'then', 'hurried', 'on', ',', 'Alice', 'started', 'to', 'her', 'feet', ',', 'for', 'it', 'flashed', 'across', 'her', 'mind', 'that', 'she', 'had', 'never', 'before', 'seen', 'a', 'rabbit', 'with', 'either', 'a', 'waistcoat', '-', 'pocket', ',', 'or', 'a', 'watch', 'to', 'take', 'out', 'of', 'it', ',', 'and', 'burning', 'with', 'curiosity', ',', 'she', 'ran', 'across', 'the', 'field', 'after', 'it', ',', 'and', 'fortunately', 'was', 'just', 'in', 'time', 'to', 'see', 'it', 'pop', 'down', 'a', 'large', 'rabbit', '-', 'hole', 'under', 'the', 'hedge', '.']]\n", "-------------------\n" ] } ], "source": [ "# cargo la versión de Alicia segmentada en párrafos\n", "alice_paras = gutenberg.paras(\"carroll-alice.txt\")\n", "print(\"Alice tiene\", len(alice_paras), \"párrafos\")\n", "\n", "# imprimo los cinco primeros\n", "for para in alice_paras[:5]:\n", " print(para)\n", " print(\"-------------------\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fíjate en que cada método devuelve una estructura de datos diferente: desde una única cadena a listas de listas anidadas. Para que tengas claro las dimensiones de cada uno, podemos imprimir el número de caracteres, palabras, oraciones y párrafos del libro. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "144395 caracteres\n", "34110 palabras\n", "1703 oraciones\n", "817 párrafos\n" ] } ], "source": [ "print(len(alice), \"caracteres\") \n", "print(len(alice_words), \"palabras\")\n", "print(len(alice_sents), \"oraciones\")\n", "print(len(alice_paras), \"párrafos\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos a imprimir algunas estadísticas para todos los libros del Proyecto Gutenberg disponibles. Para cada libro, impriremos por pantalla el promedio de caracteres por palabra, el promedio de palabras por oración y el promedio de oraciones por párrafo." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "austen-emma \t 4.61 \t 24.82 \t 3.27\n", "austen-persuasion \t 4.75 \t 26.2 \t 3.63\n", "austen-sense \t 4.75 \t 28.32 \t 2.68\n", "bible-kjv \t 4.29 \t 33.57 \t 1.22\n", "blake-poems \t 4.57 \t 19.07 \t 1.54\n", "bryant-stories \t 4.49 \t 19.41 \t 2.4\n", "burgess-busterbrown \t 4.46 \t 17.99 \t 3.96\n", "carroll-alice \t 4.23 \t 20.03 \t 2.08\n", "chesterton-ball \t 4.72 \t 20.3 \t 2.98\n", "chesterton-brown \t 4.72 \t 22.61 \t 3.28\n", "chesterton-thursday \t 4.63 \t 18.5 \t 2.91\n", "edgeworth-parents \t 4.44 \t 20.59 \t 2.75\n", "melville-moby_dick \t 4.77 \t 25.93 \t 3.6\n", "milton-paradise \t 4.84 \t 52.31 \t 63.83\n", "shakespeare-caesar \t 4.35 \t 11.94 \t 2.91\n", "shakespeare-hamlet \t 4.36 \t 12.03 \t 3.27\n", "shakespeare-macbeth \t 4.34 \t 12.13 \t 2.81\n", "whitman-leaves \t 4.59 \t 36.44 \t 1.72\n" ] } ], "source": [ "# para cada libro que está disponible en el objeto gutenberg\n", "for libro in gutenberg.fileids():\n", " caracteres = len(gutenberg.raw(libro))\n", " palabras = len(gutenberg.words(libro))\n", " oraciones = len(gutenberg.sents(libro))\n", " parrafos = len(gutenberg.paras(libro))\n", " print(libro[:-4], \"\\t\", round(caracteres/palabras, 2), \"\\t\", round(palabras/oraciones, 2), \"\\t\", round(oraciones/parrafos, 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El módulo `nltk.corpus` permite acceder a otras colecciones de textos en otras lenguas ([lista completa aquí](http://www.nltk.org/book/ch02.html#tab-corpora)). Vamos a probar con un corpus de noticias en castellano llamado `cess_esp` que incluye anotación morfo-sintáctica." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['El', 'grupo', 'estatal', 'Electricité_de_France', '-Fpa-', 'EDF', '-Fpt-', 'anunció', 'hoy', ',', 'jueves', ',', 'la', 'compra', 'del', '51_por_ciento', 'de', 'la', 'empresa', 'mexicana', 'Electricidad_Águila_de_Altamira', '-Fpa-', 'EAA', '-Fpt-', ',', 'creada', 'por', 'el', 'japonés', 'Mitsubishi_Corporation', 'para', 'poner_en_marcha', 'una', 'central', 'de', 'gas', 'de', '495', 'megavatios', '.', 'Una', 'portavoz', 'de', 'EDF', 'explicó', 'a', 'EFE', 'que', 'el', 'proyecto']\n", "----------------------\n", "[['El', 'grupo', 'estatal', 'Electricité_de_France', '-Fpa-', 'EDF', '-Fpt-', 'anunció', 'hoy', ',', 'jueves', ',', 'la', 'compra', 'del', '51_por_ciento', 'de', 'la', 'empresa', 'mexicana', 'Electricidad_Águila_de_Altamira', '-Fpa-', 'EAA', '-Fpt-', ',', 'creada', 'por', 'el', 'japonés', 'Mitsubishi_Corporation', 'para', 'poner_en_marcha', 'una', 'central', 'de', 'gas', 'de', '495', 'megavatios', '.'], ['Una', 'portavoz', 'de', 'EDF', 'explicó', 'a', 'EFE', 'que', 'el', 'proyecto', 'para', 'la', 'construcción', 'de', 'Altamira_2', ',', 'al', 'norte', 'de', 'Tampico', ',', 'prevé', 'la', 'utilización', 'de', 'gas', 'natural', 'como', 'combustible', 'principal', 'en', 'una', 'central', 'de', 'ciclo', 'combinado', 'que', 'debe', 'empezar', 'a', 'funcionar', 'en', 'mayo_del_2002', '.'], ['La', 'electricidad', 'producida', 'pasará', 'a', 'la', 'red', 'eléctrica', 'pública', 'de', 'México', 'en_virtud_de', 'un', 'acuerdo', 'de', 'venta', 'de', 'energía', 'de', 'EAA', 'con', 'la', 'Comisión_Federal_de_Electricidad', '-Fpa-', 'CFE', '-Fpt-', 'por', 'una', 'duración', 'de', '25', 'ańos', '.'], ['EDF', ',', 'que', 'no', 'quiso', 'revelar', 'cuánto', '*0*', 'pagó', 'por', 'su', 'participación', 'mayoritaria', 'en', 'EAA', ',', 'intervendrá', 'como', 'asistente', 'en', 'la', 'construcción', 'de', 'Altamira_2', 'y', ',', 'posteriormente', ',', '*0*', 'se', 'encargará', 'de', 'explotarla', 'como', 'principal', 'accionista', '.'], ['EDF', 'y', 'Mitsubishi', 'participaron', 'en', '1998', 'en', 'la', 'licitación', 'de', 'licencias', 'para', 'construir', 'centrales', 'eléctricas', 'en', 'México', 'y', '*0*', 'se', 'quedaron', 'con', 'dos', 'cada', 'una', ':', 'Río_Bravo', 'y', 'Saltillo', 'para', 'la', 'compańía', 'francesa', 'y', 'Altamira', 'y', 'Tuxpán', 'para', 'la', 'japonesa', '.']]\n" ] } ], "source": [ "from nltk.corpus import cess_esp\n", "\n", "# la versión en crudo de este corpus contiene información morfosintáctica con un formato que todavía no conocemos.\n", "# en este caso, pasamos directamente a trabajar con los textos segmentados\n", "\n", "# cargo el primer documento del corpus segmentado en palabras\n", "palabras = cess_esp.words(cess_esp.fileids()[0])\n", "print(palabras[:50])\n", "\n", "print(\"----------------------\")\n", "\n", "# y segmentado en oraciones\n", "oraciones = cess_esp.sents(cess_esp.fileids()[0])\n", "print(oraciones[:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "De manera similar a como hemos hecho sacando estadísticias de las obras disponibles en el corpus `gutenberg`, vamos a calcular la longitud promedio de palabras y el número de palabras promedio por oración, para los diez primeros documentos de este corpus `cess_esp`. \n", "\n", "Fíjate en la estructura de este ejemplo: contiene bucles anidados." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10017_20000413 \t 5.04 \t 42.17\n", "10044_20000313 \t 4.27 \t 47.62\n", "10049_20001114 \t 5.84 \t 31.11\n", "10055_20000713 \t 4.98 \t 30.89\n", "10080_20000914 \t 4.6 \t 34.0\n", "10084_20000313_1 \t 4.54 \t 42.36\n", "10084_20000313_2 \t 4.71 \t 24.0\n", "10127_20001013_1 \t 4.63 \t 40.1\n", "10127_20001013_2 \t 4.71 \t 32.1\n", "10127_20001013_3 \t 4.72 \t 35.29\n" ] } ], "source": [ "# para cada documento que está entre los 10 primeros del corpus\n", "for documento in cess_esp.fileids()[:10]: \n", " # carga el texto segmentado en palabras\n", " palabras = cess_esp.words(documento)\n", " # y en oraciones\n", " oraciones = cess_esp.sents(documento)\n", " \n", " # pon el contador de caracteres a 0\n", " caracteres = 0 \n", " # para cada palabra dentro de la lista de palabras del documento\n", " for palabra in palabras:\n", " # ve sumando al contador el número de caracteres que tiene la palabra en cuestión\n", " caracteres = caracteres + len(palabra)\n", " \n", " # cuando hayas terminado, divide la longitud total del texto entre el número de palabras\n", " longitud_promedio = caracteres / len(palabras)\n", " \n", " # imprime el nombre del documento, la longitud de la palabra y el número de palabras por oración\n", " print(documento[:-4], \"\\t\", round(longitud_promedio, 2), \"\\t\", round(len(palabras)/len(oraciones), 2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Los libros del Proyecto Gutenberg constituyen el tipo de corpus más sencillo: no está anotado (no incluye ningún tipo de información lingüística) ni categorizado. \n", "\n", "## Corpus categorizados y anotados: el Corpus de Brown\n", "\n", "El Corpus de Brown fue el primer gran corpus orientado a tareas de PLN. Desarrollado en la Universidad de Brown, contiene más de un millón de palabras provenientes de 500 fuentes. La principal catacterística de este corpus es que sus textos están categorizados por género. " ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from nltk.corpus import brown" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como en los libros del Proyecto Gutenberg, aquí también podemos imprimir los nombres de los ficheros. En este caso son poco significativos, nos nos dicen nada del contenido." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "500\n", "['ca01', 'ca02', 'ca03', 'ca04', 'ca05', 'ca06', 'ca07', 'ca08', 'ca09', 'ca10']\n" ] } ], "source": [ "# Brown está formado por 500 documentos\n", "print(len(brown.fileids()))\n", "# imprimimos solos los 10 primeros\n", "print(brown.fileids()[:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### El corpus de Brown está categorizado por géneros\n", "\n", "Una de las principales diferencias con otros corpus vistos anteriormente es que el de Brown está categorizado: los textos están agrupados según su género o temática. Y en este caso, los nombres de las categorías sí nos permiten intuir el contenido de los textos." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['adventure', 'belles_lettres', 'editorial', 'fiction', 'government', 'hobbies', 'humor', 'learned', 'lore', 'mystery', 'news', 'religion', 'reviews', 'romance', 'science_fiction']\n" ] } ], "source": [ "print(brown.categories())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "De manera similar a los libros del Proyecto Gutenberg, podemos acceder a los textos de este corpus a través de los métodos `brown.raw`, `brown.words`, `brown.sents` y `brown.paras`. Además, podemos acceder a una categoría de textos concretas si lo especificamos como argumento." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "news_words = brown.words(categories=\"news\")\n", "scifi_sents = brown.sents(categories=\"science_fiction\")" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', 'Friday', 'an', 'investigation', 'of', \"Atlanta's\", 'recent', 'primary', 'election', 'produced', '``', 'no', 'evidence', \"''\", 'that', 'any', 'irregularities', 'took', 'place', '.', 'The', 'jury', 'further', 'said', 'in', 'term-end', 'presentments', 'that', 'the', 'City', 'Executive', 'Committee', ',', 'which', 'had', 'over-all', 'charge', 'of', 'the', 'election', ',', '``', 'deserves', 'the', 'praise']\n", "-----------------------\n", "[['Now', 'that', 'he', 'knew', 'himself', 'to', 'be', 'self', 'he', 'was', 'free', 'to', 'grok', 'ever', 'closer', 'to', 'his', 'brothers', ',', 'merge', 'without', 'let', '.'], [\"Self's\", 'integrity', 'was', 'and', 'is', 'and', 'ever', 'had', 'been', '.'], ['Mike', 'stopped', 'to', 'cherish', 'all', 'his', 'brother', 'selves', ',', 'the', 'many', 'threes-fulfilled', 'on', 'Mars', ',', 'corporate', 'and', 'discorporate', ',', 'the', 'precious', 'few', 'on', 'Earth', '--', 'the', 'unknown', 'powers', 'of', 'three', 'on', 'Earth', 'that', 'would', 'be', 'his', 'to', 'merge', 'with', 'and', 'cherish', 'now', 'that', 'at', 'last', 'long', 'waiting', 'he', 'grokked', 'and', 'cherished', 'himself', '.']]\n" ] } ], "source": [ "print(news_words[:50])\n", "print(\"-----------------------\")\n", "print(scifi_sents[:3])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos a sacar provecho de la categorización de los textos de este corpus. Para ello, vamos a calcular la frecuencia de distribución de distintos verbos modales para cada categoría. Para ello vamos a calcular una distribución de frecuencia condicional que calcule la frecuencia de cada palabra para cada categoría.\n", "\n", "No te preocupes si no entiendes la sintaxis para crear tablas de frecuencias condicionales a través del objeto `ConditionalFreqDist`. Créeme, ese objeto calcula frecuencias de palabras atendiendo a la categoría en la que aparecen y crea una especie de diccionario de diccionarios." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from nltk import ConditionalFreqDist\n", "modals = \"can could would should must may might\".split()\n", "modals_cfd = ConditionalFreqDist(\n", " (category, word) \n", " for category in brown.categories() \n", " for word in brown.words(categories=category)\n", " )\n", "\n", "# la sintaxis anterior de anidar bucles for es un poco compleja y no la hemos visto\n", "# sin necesidad de profundizar más, las últimas tres líneas son equivalentes a:\n", "# for category in brown.categories():\n", "# for word in brown.words(categories=category):\n", "# ConditionalFreqDist(category, word)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Una vez tenemos calculada la frecuencia de distribución condicional, podemos pintar los valores fácilmente en forma de tabla a través del método `.tabulate`, especificando como condiciones cada una de las categorías, y como muestras los verbos modales del inglés que hemos definido." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " can could would should must may might \n", " adventure 46 151 191 15 27 5 58 \n", " belles_lettres 246 213 392 102 170 207 113 \n", " editorial 121 56 180 88 53 74 39 \n", " fiction 37 166 287 35 55 8 44 \n", " government 117 38 120 112 102 153 13 \n", " hobbies 268 58 78 73 83 131 22 \n", " humor 16 30 56 7 9 8 8 \n", " learned 365 159 319 171 202 324 128 \n", " lore 170 141 186 76 96 165 49 \n", " mystery 42 141 186 29 30 13 57 \n", " news 93 86 244 59 50 66 38 \n", " religion 82 59 68 45 54 78 12 \n", " reviews 45 40 47 18 19 45 26 \n", " romance 74 193 244 32 45 11 51 \n", "science_fiction 16 49 79 3 8 4 12 \n", " can should would \n", "fiction 37 35 287 \n" ] } ], "source": [ "modals_cfd.tabulate(conditions=brown.categories(), samples=modals)\n", "\n", "# imprimo solo algunos verbos modales para la categoría fiction\n", "modals_cfd.tabulate(conditions=[\"fiction\"], samples=[\"can\", \"should\", \"would\"])\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Las cifras que hemos mostrado en las tablas anteriores se refieren a las frecuencias absolutas de cada verbo modal en cada categoría. Realizar comparaciones así no es acertado, porque es posible que cada categoría tenga un número de documentos (y de palabras) diferente. \n", "\n", "Vamos a comprobar si esto es cierto. ¿Está equilibrada la colección o tenemos algunos géneros sobrerrepresentados?" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "adventure 69342\n", "belles_lettres 173096\n", "editorial 61604\n", "fiction 68488\n", "government 70117\n", "hobbies 82345\n", "humor 21695\n", "learned 181888\n", "lore 110299\n", "mystery 57169\n", "news 100554\n", "religion 39399\n", "reviews 40704\n", "romance 70022\n", "science_fiction 14470\n" ] } ], "source": [ "for categoria in brown.categories():\n", " print(categoria, len(brown.words(categories=categoria)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como vemos, el número de palabras no está equilibado. Tenemos muchos más datos en las categorías `belles_lettres` y `learned` que en `science_fiction` o `humor`, por ejemplo.\n", "\n", "Calculemos a continuación la frecuencia relativa de estos verbos modales, atendiendo al género. Para ello, necesitamos dividir la frecuencia absoluta de cada modal entre el número de palabras total de cada categoría." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " adventure\n", "----------------------\n", "can -> 0.0006633786161345216\n", "could -> 0.002177612413832886\n", "would -> 0.0027544633843846443\n", "should -> 0.00021631911395690923\n", "must -> 0.00038937440512243663\n", "may -> 7.210637131896974e-05\n", "might -> 0.000836433907300049\n", "\n", " belles_lettres\n", "----------------------\n", "can -> 0.0014211766880806026\n", "could -> 0.0012305310348014974\n", "would -> 0.002264639275315432\n", "should -> 0.0005892683828626889\n", "must -> 0.000982113971437815\n", "may -> 0.001195868188750751\n", "might -> 0.000652816933955724\n", "\n", " editorial\n", "----------------------\n", "can -> 0.001964158171547302\n", "could -> 0.0009090318810466853\n", "would -> 0.0029218881890786313\n", "should -> 0.0014284786702162197\n", "must -> 0.0008603337445620414\n", "may -> 0.0012012206999545483\n", "might -> 0.0006330757743003701\n", "\n", " fiction\n", "----------------------\n", "can -> 0.0005402406260950823\n", "could -> 0.002423782268426586\n", "would -> 0.004190515126737531\n", "should -> 0.0005110384300899427\n", "must -> 0.0008030603901413386\n", "may -> 0.00011680878402055835\n", "might -> 0.000642448312113071\n", "\n", " government\n", "----------------------\n", "can -> 0.0016686395595932513\n", "could -> 0.0005419513099533637\n", "would -> 0.0017114251893264115\n", "should -> 0.0015973301767046508\n", "must -> 0.0014547114109274499\n", "may -> 0.0021820671163911747\n", "might -> 0.00018540439551036124\n", "\n", " hobbies\n", "----------------------\n", "can -> 0.0032545995506709576\n", "could -> 0.0007043536341004311\n", "would -> 0.0009472341975833384\n", "should -> 0.0008865140567126115\n", "must -> 0.0010079543384540653\n", "may -> 0.0015908676908130428\n", "might -> 0.000267168619831198\n", "\n", " humor\n", "----------------------\n", "can -> 0.0007374971191518783\n", "could -> 0.0013828070984097719\n", "would -> 0.0025812399170315743\n", "should -> 0.0003226549896289468\n", "must -> 0.00041484212952293156\n", "may -> 0.00036874855957593915\n", "might -> 0.00036874855957593915\n", "\n", " learned\n", "----------------------\n", "can -> 0.002006729415904293\n", "could -> 0.0008741643209007741\n", "would -> 0.001753826530612245\n", "should -> 0.0009401389866291344\n", "must -> 0.0011105735397607319\n", "may -> 0.0017813159746657284\n", "might -> 0.0007037297677691766\n", "\n", " lore\n", "----------------------\n", "can -> 0.0015412651066646116\n", "could -> 0.0012783434119982956\n", "would -> 0.0016863253519977515\n", "should -> 0.0006890361653324146\n", "must -> 0.0008703614719988395\n", "may -> 0.0014959337799980055\n", "might -> 0.000444247001332741\n", "\n", " mystery\n", "----------------------\n", "can -> 0.0007346638912697441\n", "could -> 0.002466371634976998\n", "would -> 0.0032535115184802953\n", "should -> 0.0005072679249243471\n", "must -> 0.0005247599223355315\n", "may -> 0.00022739596634539699\n", "might -> 0.0009970438524375099\n", "\n", " news\n", "----------------------\n", "can -> 0.0009248761859299481\n", "could -> 0.0008552618493545757\n", "would -> 0.002426556874912982\n", "should -> 0.0005867494082781391\n", "must -> 0.0004972452612526602\n", "may -> 0.0006563637448535115\n", "might -> 0.0003779063985520218\n", "\n", " religion\n", "----------------------\n", "can -> 0.002081271098251225\n", "could -> 0.0014974999365466128\n", "would -> 0.0017259321302571132\n", "should -> 0.0011421609685525014\n", "must -> 0.0013705931622630017\n", "may -> 0.0019797456788243355\n", "might -> 0.000304576258280667\n", "\n", " reviews\n", "----------------------\n", "can -> 0.0011055424528301887\n", "could -> 0.0009827044025157233\n", "would -> 0.0011546776729559748\n", "should -> 0.0004422169811320755\n", "must -> 0.00046678459119496856\n", "may -> 0.0011055424528301887\n", "might -> 0.0006387578616352201\n", "\n", " romance\n", "----------------------\n", "can -> 0.0010568107166319157\n", "could -> 0.0027562765987832393\n", "would -> 0.0034846191197052353\n", "should -> 0.0004569992288138014\n", "must -> 0.0006426551655194082\n", "may -> 0.00015709348490474423\n", "might -> 0.000728342520921996\n", "\n", " science_fiction\n", "----------------------\n", "can -> 0.0011057360055286801\n", "could -> 0.0033863165169315825\n", "would -> 0.005459571527297858\n", "should -> 0.0002073255010366275\n", "must -> 0.0005528680027643401\n", "may -> 0.00027643400138217003\n", "might -> 0.00082930200414651\n" ] } ], "source": [ "for categoria in brown.categories():\n", " # ¿cuántas palabras tenemos en cada categoría?\n", " longitud = len(brown.words(categories=categoria))\n", " print(\"\\n\", categoria)\n", " print(\"----------------------\")\n", " for palabra in modals:\n", " print(palabra, \"->\", modals_cfd[categoria][palabra]/longitud)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos a repetir la operación de cálculo de frecuencias relativas reasignando estos valores en el propio objeto `modals_cfd`, con el objetivo de utilizar el método `tabulate` para poder impirmir la tabla con los valors relativos." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# lo primero, realizo una copia de mi distribución de frecuencias \n", "import copy\n", "modals_cfd_rel = copy.deepcopy(modals_cfd)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# sustituyo los conteos de la tabla por sus frecuencias relativas (ojo, en tantos por 10.000)\n", "for categoria in brown.categories():\n", " longitud = len(brown.words(categories=categoria))\n", " for palabra in modals:\n", " modals_cfd_rel[categoria][palabra] = (modals_cfd[categoria][palabra]/longitud)*10000" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " can could would should must may might \n", " adventure 6 21 27 2 3 0 8 \n", " belles_lettres 14 12 22 5 9 11 6 \n", " editorial 19 9 29 14 8 12 6 \n", " fiction 5 24 41 5 8 1 6 \n", " government 16 5 17 15 14 21 1 \n", " hobbies 32 7 9 8 10 15 2 \n", " humor 7 13 25 3 4 3 3 \n", " learned 20 8 17 9 11 17 7 \n", " lore 15 12 16 6 8 14 4 \n", " mystery 7 24 32 5 5 2 9 \n", " news 9 8 24 5 4 6 3 \n", " religion 20 14 17 11 13 19 3 \n", " reviews 11 9 11 4 4 11 6 \n", " romance 10 27 34 4 6 1 7 \n", "science_fiction 11 33 54 2 5 2 8 \n" ] } ], "source": [ "# imprimo la tabla \n", "modals_cfd_rel.tabulate(conditions=brown.categories(), samples=modals)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Brown es también un corpus anotado con información morfológica\n", "\n", "El corpus de Brown no solo está categorizado, también está anotado con información morfológica. Para acceder a la versión anotada del corpus, podemos utilizar los métodos: `brown.tagged_words`, `brown.tagged_sents` y `brown.tagged_\n", "paras`" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[('Now', 'RB'), ('that', 'CS'), ('he', 'PPS'), ('knew', 'VBD'), ('himself', 'PPL'), ('to', 'TO'), ('be', 'BE'), ('self', 'NN'), ('he', 'PPS'), ('was', 'BEDZ'), ('free', 'JJ'), ('to', 'TO'), ('grok', 'VB'), ('ever', 'QL'), ('closer', 'RBR'), ('to', 'IN'), ('his', 'PP$'), ('brothers', 'NNS'), (',', ','), ('merge', 'VB')]\n" ] } ], "source": [ "scifi_tagged_words = brown.tagged_words(categories=\"science_fiction\")\n", "print(scifi_tagged_words[:20])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fíjate que cuando accedemos a la versión etiquetada del corpus, no obtenemos una simple lista de palabras sino una lista de tuplas, donde el primer elemento es la palabra en cuestión u el segundo es la etiqueta que indica la categoría gramatical de la palabra.\n", "\n", "Este conjunto etiquetas se ha convertido en casi un estándar para el inglés y se utilizan habitualmente para anotar cualquier recurso lingüístico en esa lengua.\n", "\n", "Vamos a crear una nueva frecuencia de distribución condicional para calcular la frecuencia de aparición de las etiquetas, teniendo en cuenta la categoría." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": true }, "outputs": [], "source": [ "tags_cfd = ConditionalFreqDist(\n", " (category, item[1])\n", " for category in brown.categories()\n", " for item in brown.tagged_words(categories=category)\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y ahora vamos a imprimir la tabla de frecuencias para cada categoría y para algunas de las etiquetas morfológicas: sustantivos en singular `NN`, verbos en presente `VB`, verbos en pasado simple `VBD`, participios pasados `VBN`, adjetivos `JJ`, preposiciones `IN`, y artículos `AT`. \n", "\n", "Recuerda: estas cifras no son directamente comparables entre categorías ya que éstas no están equilibradas. Hay categorías con más textos que otras." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " NN VB VBD VBN JJ IN AT \n", " adventure 8051 2170 3702 1276 2687 5908 5531 \n", " belles_lettres 21800 4829 3501 4223 10414 19083 14898 \n", " editorial 7675 2129 700 1491 3593 6204 5311 \n", " fiction 7815 2173 3027 1497 2958 6012 5439 \n", " government 9877 1833 405 2190 4173 8596 5716 \n", " hobbies 12465 2966 617 2252 4883 8591 6946 \n", " humor 2567 656 699 478 1078 1926 1655 \n", " learned 29194 4342 1481 6044 12294 21757 16828 \n", " lore 14707 3083 2272 2822 6475 12074 9936 \n", " mystery 6461 2026 2645 1161 2109 4692 4321 \n", " news 13162 2440 2524 2269 4392 10616 8893 \n", " religion 4923 1275 511 931 2327 4266 3327 \n", " reviews 5066 872 504 875 2742 4040 3447 \n", " romance 7166 2404 3048 1359 3180 5616 4671 \n", "science_fiction 1541 495 531 318 723 1176 1040 \n" ] } ], "source": [ "tags_cfd.tabulate(conditions=brown.categories(), \n", " samples=\"NN VB VBD VBN JJ IN AT\".split())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Veamos otro ejemplo: creamos una lista de adjetivos (etiquetados como JJ) que aparezcan en la colección de textos sobre hobbies, e imprimirmos los 50 primeros que encontramos" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4883\n", "['old', 'childish', 'genuine', 'happy', 'unkind', 'happy', 'sunny', 'new', 'fast', 'fast', 'good', 'famous', 'prize-winning', 'tall', 'astounding', 'famous', 'fast', 'wonder-working', 'crazy', 'vigorous', 'solid', 'skinny', 'shapely', 'upper', 'muscular', 'symmetrical', 'real', 'now-famous', 'specific', 'famous', 'upper', 'collar-to-collar', 'wide', 'Reeves-type', 'upper', 'frontal', 'entire', 'chest-back-shoulder', 'alternate', 'alternate', 'complete', 'complete', 'five-minute', 'complete', 'similar', 'flat', 'downward', 'possible', 'upper', 'true']\n" ] } ], "source": [ "# creo una lista vacía de adjetivos\n", "adjetivos = []\n", "\n", "# itero sobre las tuplas de la categorías hobbies\n", "for tupla in brown.tagged_words(categories=\"hobbies\"):\n", " # compruebo que son adjetivos\n", " if tupla[1] == \"JJ\":\n", " # guardo la palabra en cuestión en mis lista de adjetivos\n", " adjetivos.append(tupla[0])\n", " \n", "# hay bastantes \n", "print(len(adjetivos)) \n", "# así que, solo imprimo solo los 50 primeros\n", "print(adjetivos[:50]) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Otro ejempo: Para cada categoría del corpus de Brown, imprimimos solo aquellos adjetivos que tengan una longitud de al menos 15 caracteres y que no sean palabras compuestas escritas con guiones ortográficos." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_keys(['straightforward', 'intergovernmental', 'indistinguishable', 'interdenominational', 'underprivileged', 'interchangeable', 'neuropsychiatric', 'Physicochemical', 'uncommunicative', 'intradepartmental', 'intercollegiate', 'anthropological', 'intraepithelial', 'incontrovertible', 'disproportionate', 'bibliographical', 'noncommissioned', 'anthropomorphic', 'communicational', 'encephalographic', 'nondiscriminatory', 'crystallographic', 'incomprehensible', 'nonagricultural', 'parasympathetic', 'photoelectronic', 'substitutionary', 'lexicostatistic', 'unreconstructed', 'interdepartmental', 'semiquantitative', 'phenomenological', 'macropathological', 'spectrophotometric', 'Crystallographic', 'Thermogravimetric', 'impressionistic', 'inconsequential', 'psychotherapeutic', 'distinguishable', 'intercontinental', 'undistinguished', 'cathodoluminescent', 'extraterrestrial', 'glottochronological', 'micrometeoritic', 'trichloroacetic', 'chromatographic', 'undifferentiated', 'internationalist', 'unconstitutional', 'traditionalistic', 'gastrointestinal', 'particularistic', 'psychopharmacological', 'pharmacological', 'indiscriminating', 'cathodophoretic', 'individualistic', 'substerilization', 'multidimensional', 'autobiographical', 'nonmythological', 'polycrystalline', 'expressionistic', 'unsophisticated'])\n" ] } ], "source": [ "# en este caso, creo un diccionario vacío\n", "adjetivos = {}\n", "\n", "# itero sobre las categorías\n", "for categoria in brown.categories():\n", " for elemento in brown.tagged_words():\n", " if elemento[1] == \"JJ\":\n", " if len(elemento[0]) >= 15:\n", " if \"-\" not in elemento[0]:\n", " # cuando encuentro una palabra que cumple las tres condiciones, la almacenos en mi diccionario de adjetivos\n", " adjetivos[elemento[0]] = 1\n", "\n", "# por último, imprimo las claves de mi diccionario\n", "print(adjetivos.keys())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Recursos Léxicos: WordNet\n", "\n", "Wordnet es una red semántica para el inglés. En esencia, es similar a un diccionario pero está organizado por *synsets* (conjunto de palabras sinónimas) y no por lemas. \n", "\n", "Podemos acceder a WordNet a través de NLTK:\n", " " ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from nltk.corpus import wordnet as wn" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para consultar los synsets en los que aparece una determinada palabra, podemos utilizar el método `.synsets` como se muestra en el ejemplo. Como resultado obtenemos una lista con todos los synsets en los que aparece la palabra." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Synset('sword.n.01')]\n", "[Synset('car.n.01'), Synset('car.n.02'), Synset('car.n.03'), Synset('car.n.04'), Synset('cable_car.n.01')]\n" ] } ], "source": [ "# buscamos los synsets en los que aparece la palabra sword\n", "print(wn.synsets(\"sword\"))\n", "\n", "# y buscamos car\n", "print(wn.synsets(\"car\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En este caso, la palabra *sword* solo aparece en un synset, lo que implica que solo tiene un sentido. Además, sabemos que es un sustantivo, porque el nombre de synset está etiquetado como `n`.\n", "\n", "Por su parte, la palabra *car* es polisémica y aparece en cinco sentidos, toso ellos sustantivos.\n", "\n", "Si guardo el synset en cuestión en una variable (fíjate que me quedo con el primer elemento de la lista que me devuelve el método `wn.synsets`), podemos acceder a distintos métodos:" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['sword', 'blade', 'brand', 'steel']\n", "a cutting or thrusting weapon that has a long metal blade and a hilt with a hand guard\n", "['cable_car', 'car'] a conveyance for passengers or freight on a cable railway\n", "['cable_car', 'car'] a conveyance for passengers or freight on a cable railway\n", "['they took a cable car to the top of the mountain']\n" ] } ], "source": [ "sword = wn.synsets(\"sword\")[0]\n", "print(sword.lemma_names()) # imprime los lemas del synset => sinónimos\n", "print(sword.definition()) # imprime la definición del synset\n", "\n", "# hacemos lo mismo con car\n", "car = wn.synsets(\"car\")\n", "cable_car = car[-1]\n", "print(cable_car.lemma_names(), cable_car.definition())\n", "print(car[-1].lemma_names(), car[-1].definition()) # esta línea es equivalente a la anterior. ¿Ves por qué?\n", "\n", "# imprimo las oraciones de ejemplo\n", "print(cable_car.examples())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Si escribes `sword.` y pulsas el tabulador podrás visualizar todos los métodos accesibles desde un objeto synset. Son muchos: si tienes interés en alguno que no se menciona en este resumen, pregúntame o consulta el libro de NLTK.\n", "\n", "Entre las cosas que sí nos interesan está el poder acceder a relaciones como hiponimia, meronimia, etc. Por ejemplo, para acceder a todos los hipónimos de *sword* con el sentido de *espada*, es decir, a todos los **tipos de** *espada* y a sus definiciones." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Synset('backsword.n.02'), Synset('broadsword.n.01'), Synset('cavalry_sword.n.01'), Synset('cutlas.n.01'), Synset('falchion.n.01'), Synset('fencing_sword.n.01'), Synset('rapier.n.01')]\n", "['backsword']\n", "a sword with only one cutting edge\n", "['broadsword']\n", "a sword with a broad blade and (usually) two cutting edges; used to cut rather than stab\n", "['cavalry_sword', 'saber', 'sabre']\n", "a stout sword with a curved blade and thick back\n", "['cutlas', 'cutlass']\n", "a short heavy curved sword with one edge; formerly used by sailors\n", "['falchion']\n", "a short broad slightly convex medieval sword with a sharp point\n", "['fencing_sword']\n", "a sword used in the sport of fencing\n", "['rapier', 'tuck']\n", "a straight sword with a narrow blade and two edges\n" ] } ], "source": [ "print(sword.hyponyms())\n", "\n", "for element in sword.hyponyms():\n", " print(element.lemma_names())\n", " print(element.definition())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "En lugar de bajar hasta los elementos más específicos, podemos navegar en la jerarquía de sentidos hasta los synsets más generales. Por ejemplo, podemos acceder a los hiperónimos inmediatos de un synset a través del método `.hypernyms()`:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['weapon', 'arm', 'weapon_system']\n", "any instrument or instrumentality used in fighting or hunting\n" ] } ], "source": [ "for element in sword.hypernyms():\n", " print(element.lemma_names())\n", " print(element.definition())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fíjate que con este método sólo subimos un nivel hacia el synset más general. En este caso, comprobamos que *sword* es un tipo de *weapon* o *arm*. Si, por el contrario, lo que nos interesa es acceder a todos los hiperónimos de *sword*, navegando hasta el elemento raíz de la jerarquía de WordNet (que siempre es *entity*), podemos utilizar el método `.hypernym_paths()`:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['entity']\n", "that which is perceived or known or inferred to have its own distinct existence (living or nonliving)\n", "['physical_entity']\n", "an entity that has physical existence\n", "['object', 'physical_object']\n", "a tangible and visible entity; an entity that can cast a shadow\n", "['whole', 'unit']\n", "an assemblage of parts that is regarded as a single entity\n", "['artifact', 'artefact']\n", "a man-made object taken as a whole\n", "['instrumentality', 'instrumentation']\n", "an artifact (or system of artifacts) that is instrumental in accomplishing some end\n", "['device']\n", "an instrumentality invented for a particular purpose\n", "['instrument']\n", "a device that requires skill for proper use\n", "['weapon', 'arm', 'weapon_system']\n", "any instrument or instrumentality used in fighting or hunting\n", "['sword', 'blade', 'brand', 'steel']\n", "a cutting or thrusting weapon that has a long metal blade and a hilt with a hand guard\n" ] } ], "source": [ "for path in sword.hypernym_paths():\n", " for element in path:\n", " print(element.lemma_names())\n", " print(element.definition())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fíjate que `.hypernym_paths()` me devuelve una lista de caminos posibles desde el synset en cuestión hasta el elemento *entity*. Por eso itero sobre los elementos `path` que me devuelve `.hypernym_paths()`. En el ejemplo de *sword*, da la casualidad de que solo hay un camino posible. Cada `path` es una lista de synsets, e itero sobre ellos. Por eso utilizo un bucle dentro de otro.\n", "\n", "Para acceder a los merónimos, es decir, a las partes o elementos constitutivos de *sword*, podemos utilizar el método `.part_meronyms()`, como se muestra a continuación." ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['blade']\n", "the flat part of a tool or weapon that (usually) has a cutting edge\n", "['foible']\n", "the weaker part of a sword's blade from the forte to the tip\n", "['forte']\n", "the stronger part of a sword blade between the hilt and the foible\n", "['haft', 'helve']\n", "the handle of a weapon or tool\n", "['hilt']\n", "the handle of a sword or dagger\n", "['point', 'tip', 'peak']\n", "a V shape\n" ] } ], "source": [ "for element in sword.part_meronyms():\n", " print(element.lemma_names())\n", " print(element.definition())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "De manera similar, podemos acceder a los holónimos de un synset, es decir, a los elementos de los que *espada* forma parte, a través del método `.part_holonyms()`. El synset que estamos utilizando no tiene definidos holónimos, así que el ejemplo devuelve una lista vacía." ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "print(sword.part_holonyms())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Busquemos ahora algún ejemplo que tenga otros tipos de merónimos." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['water', 'H2O']\n", "['body_of_water', 'water']\n", "['water']\n", "['water_system', 'water_supply', 'water']\n", "['urine', 'piss', 'pee', 'piddle', 'weewee', 'water']\n", "['water']\n", "['water', 'irrigate']\n", "['water']\n", "['water']\n", "['water']\n", "[Synset('hydrogen.n.01'), Synset('oxygen.n.01')]\n", "['air'] a mixture of gases (especially oxygen) required for breathing; the stuff that the wind consists of\n", "['air'] the region above the ground\n", "['air', 'aura', 'atmosphere'] a distinctive but intangible quality surrounding a person or thing\n", "['breeze', 'zephyr', 'gentle_wind', 'air'] a slight wind (usually refreshing)\n", "['atmosphere', 'air'] the mass of air surrounding the Earth\n", "['air'] once thought to be one of four elements composing the universe (Empedocles)\n", "['tune', 'melody', 'air', 'strain', 'melodic_line', 'line', 'melodic_phrase'] a succession of notes forming a distinctive sequence\n", "['air', 'airwave'] medium for radio and television broadcasting\n", "['air_travel', 'aviation', 'air'] travel via aircraft\n", "['air_out', 'air', 'aerate'] expose to fresh air\n", "['air'] be broadcast\n", "['air', 'send', 'broadcast', 'beam', 'transmit'] broadcast over the airwaves, as in radio or television\n", "['publicize', 'publicise', 'air', 'bare'] make public\n", "['air'] expose to warm or heated air, so as to dry\n", "['vent', 'ventilate', 'air_out', 'air'] expose to cool or cold air so as to cool or freshen\n", "[Synset('argon.n.01'), Synset('krypton.n.01'), Synset('neon.n.01'), Synset('nitrogen.n.01'), Synset('oxygen.n.01'), Synset('xenon.n.01')]\n" ] } ], "source": [ "# en cuántos synsets aparece la palabra water?\n", "water = wn.synsets(\"water\")\n", "for synset in water:\n", " print(synset.lemma_names())\n", "\n", "# me quedo con el primero\n", "agua = water[0]\n", "# que tiene unos cuantos merónimos de sustancia\n", "print(agua.substance_meronyms())\n", "\n", "# ídem para air\n", "air = wn.synsets(\"air\")\n", "for synset in air:\n", " print(synset.lemma_names(), synset.definition())\n", "\n", "aire = air[0]\n", "print(aire.substance_meronyms())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hay varios métodos para acceder a distintos tipos de merónimos y holónimos, aunque no siempre están definidas estas relaciones. Cuando no están definidas, los métodos no dan error, simplemente devuelven listas vacías.\n", "\n", "Los nombres de estos métodos tratan de ser autoexplicativos: por un lado, tenemos `.part_holonyms()`, `.member_holonyms()`, `.substance_holonyms()`, y por otro, `.part_meronyms()`, `.member_meronyms()`, `.substance_meronyms()`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pequeño ejercicio\n", "\n", "- Busca los sentidos en los que aparece la palabra *bike*.\n", "- Identifica el que hace referencia a *bicicleta*: vehículo de dos ruedas a pedales\n", "- Imprime los merónimos, las partes que conforman una bicicleta\n" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "n : a motor vehicle with two wheels and a strong frame\n", "n : a wheeled vehicle that has two wheels and is moved by foot pedals\n", "v : ride a bicycle\n", "merónimos de a wheeled vehicle that has two wheels and is moved by foot pedals\n", "['bicycle_seat', 'saddle']\n", "['bicycle_wheel']\n", "['chain']\n", "['coaster_brake']\n", "['handlebar']\n", "['kickstand']\n", "['mudguard', 'splash_guard', 'splash-guard']\n", "['pedal', 'treadle', 'foot_pedal', 'foot_lever']\n", "['sprocket', 'sprocket_wheel']\n", "----------------------------\n", "hipónimos de a wheeled vehicle that has two wheels and is moved by foot pedals\n", "['bicycle-built-for-two', 'tandem_bicycle', 'tandem']\n", "['mountain_bike', 'all-terrain_bike', 'off-roader']\n", "['ordinary', 'ordinary_bicycle']\n", "['push-bike']\n", "['safety_bicycle', 'safety_bike']\n", "['velocipede']\n" ] } ], "source": [ "bike_synsets = wn.synsets(\"bike\")\n", "for s in bike_synsets:\n", " print(s.pos(), \":\", s.definition())\n", " \n", "print(\"merónimos de\", bike_synsets[1].definition()) \n", "for meronym in bike_synsets[1].part_meronyms():\n", " print(meronym.lemma_names())\n", "\n", "print(\"----------------------------\")\n", " \n", "print(\"hipónimos de\", bike_synsets[1].definition())\n", "for hipo in bike_synsets[1].hyponyms():\n", " print(hipo.lemma_names())" ] } ], "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 }