<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Resumen NLTK
Step1:  Como primer ejemplo, podemos utilizar la función nltk.pos_tag para etiquetar morfológicamente una oración en inglés, siempre que la especifiquemos como una lista de palabras o tokens.
Step2:  El etiquetador funciona bastante bien aunque comete errores, obviamente. Si probamos con la famosa frase de Chomksy detectamos palabras mal etiquetadas.
Step3:  ¿Cómo funciona este etiquetador? nltk.pos_tag es un etiquetador morfológico basado en aprendizaje automático. A partir de miles de ejemplos de oraciones etiquetadas manualmente, el sistema ha aprendido, calculando frecuencias y generalizando cuál es la categoría gramatical más probable para cada token. 
Como sabes, desde NLTK podemos acceder a corpus que ya están etiquetados. Vamos a utilizar alguno de los que ya conoces, el corpus de Brown, para entrenar nuestros propios etiquetadores.
Para ello importamos el corpus de Brown y almacenamos en un par de variables las noticias de este corpus en su versión etiquetada morfológicamente y sin etiquetar.
Step4:  Para comparar ambas versiones, podemos imprimir la primera oración de este corpus en su versión sin etiquetas (fíjate que se trata de una lista de tokens, sin más) y en su versión etiquetada (se trata de una lista de tuplas donde el primer elemento es el token y el segundo es la etiqueta morfológica).
Step5:  Etiquetado morfológico automático
Etiquetador por defecto
NLTK nos da acceso a tipos de etiquetadores morfológicos. Veamos cómo utilizar algunos de ellos.
A la hora de enfrentarnos al etiquetado morfológico de un texto, podemos adoptar una estrategia sencilla consistente en etiquetar por defecto todas las palabras con la misma categoría gramatical. Con NLTK podemos utilizar un DefaultTagger que etiquete todos los tokens como sustantivo. Las categoría sustantivo singular (NN en el esquema de etiquetas de Treebank) suele ser la más frecuente. Veamos qué tal funciona.
Step6:  Obviamente no funciona bien, pero ojo, en el ejemplo anterior con oracion1 hemos etiquetado correctamente 2 de 10 tokens. Si lo evaluamos con un corpus más grande, como el conjunto de oraciones de Brown que ya tenemos, obtenemos una precisión superior al 13%
Step7:  el método .evaluate que podemos ejecutar con cualquier etiquetador si especificamos como argumento una colección de referencia que ya esté etiquetada, nos devuelve un número
Step8:  Cuando probamos a evaluarlo con un corpus de oraciones más grande, vemos que nuestra precisión sube por encima del 32%.
Step9:  Etiquetado basado en ngramas
Antes hemos dicho que la función nltk.pos_tag tenía un etiquetador morfológico que funcionaba con información estadística. A continuación vamos a reproducir, a pequeña escala, el proceso de entrenamiento de un etiquetador morfológico basado en aprendizaje automático. 
En general, los sistemas de aprendizaje automático funcionan del siguiente modo
Step10:  A continuación vamos a crear un etiquetador basado en unigramas (secuencias de una palabra o palabras sueltas) a través de la clase nltk.UnigramTagger, proporcionando nuestro corpusEntrenamiento para que aprenda. Una vez entrenado, vamos a evaluar su rendimiento sobre corpusTest.
Step11:  Los etiquetadores basados en unigramas se construyen a partir del simple cálculo de una distribución de frecuencia para cada token (palabra) y asignan siempre la etiqueta morfológica más probable. En nuestro caso, esta estrategia funciona relativamente bien
Step12:  Como se ve en los ejemplos, los resultados son desastrosos. La mayoría de los tokens se quedan sin etiqueta y se muestran como None.
Si los evaluamos con nuestra colección de test, vemos que apenas superan el 10% de precisión. Peores resultados que nuestro DefaultTagger.
Step13:  ¿Por qué ocurre esto? La intuición no nos engaña, y es verdad que si calculamos distribuciones de frecuencia condicionales teniendo en cuenta secuencias de palabras más largas, nuestros datos serán más finos. Sin embargo, cuando consideramos secuencias de tokens más largos nos arriesgamos a que dichas secuencias no aparezcan como tales en el corpus de entrenamiento. 
En el ejemplo de oracion2, nuestro bigramTagger es incapaz de etiquetar la palabra progress porque no ha encontrado en el corpus de entrenamiento ni el bigrama (The, progress) ni (I, progress). Obviamente, nuestro trigramTagger tampoco ha encontrado los trigramas (INICIO_DE_ORACIÓN, The, progress) o (as, I, progress). Si esas secuencias no aparecen en el corpus de entrenamiento, no hay nada que aprender. 
Combinamos etiquetadores
En estos casos, la solución más satisfactoria consiste en combinar de manera incremental la potencia de todos nuestros etiquetadores. Vamos a crear nuevos taggers que utilicen otros como respaldo. 
Utilizaremos un tagger de trigramas que, cuando no tenga respuesta para etiquetar un determinado token, utilizará como respaldo el tagger de bigramas. A su vez, el tagger de bigramas tirará del de unigramas cuando no tenga respuesta. Por último, el de unigramas utilizará como respaldo el tagger de expresiones regulares que definimos antes. De esta manera aumentamos la precisión hasta casi el 87%.
Step14:  Ejercicio en clase
Vamos a mejorar nuestro etiquetador extendiendo el corpus de entrenamiento. En lugar de usar solo los textos de noticias, vamos a utilizar el corpus de Brown completo.
<ASSISTANT_TASK:>
Python Code:
import nltk
Explanation: Resumen NLTK: Etiquetado morfológico (part-of-speech tagging)
Este resumen se corresponde con el capítulo 5 del NLTK Book Categorizing and Tagging Words. La lectura del capítulo es muy recomendable.
Etiquetado morfológico con NLTK
NLTK propociona varias herramientas para poder crear fácilmente etiquetadores morfológicos (part-of-speech taggers). Veamos algunos ejemplos.
Para empezar, necesitamos importar el módulo nltk que nos da acceso a todas las funcionalidades:
End of explanation
oracion1 = 'This is the lost dog I found at the park'.split()
oracion2 = 'The progress of the humankind as I progress'.split()
print(nltk.pos_tag(oracion1))
print(nltk.pos_tag(oracion2))
Explanation: Como primer ejemplo, podemos utilizar la función nltk.pos_tag para etiquetar morfológicamente una oración en inglés, siempre que la especifiquemos como una lista de palabras o tokens.
End of explanation
oracion3 = 'Green colorless ideas sleep furiously'.split()
print(nltk.pos_tag(oracion3))
print(nltk.pos_tag(["My", "name", "is", "Prince"]))
print(nltk.pos_tag('He was born during the summer of 1988'.split()))
print(nltk.pos_tag('''She's Tony's sister'''.split()))
print(nltk.pos_tag('''My name is Sasolamatom and I have a stromkupft dog'''.split()))
Explanation: El etiquetador funciona bastante bien aunque comete errores, obviamente. Si probamos con la famosa frase de Chomksy detectamos palabras mal etiquetadas.
End of explanation
from nltk.corpus import brown
brown_sents = brown.sents(categories='news')
brown_tagged_sents = brown.tagged_sents(categories='news')
Explanation: ¿Cómo funciona este etiquetador? nltk.pos_tag es un etiquetador morfológico basado en aprendizaje automático. A partir de miles de ejemplos de oraciones etiquetadas manualmente, el sistema ha aprendido, calculando frecuencias y generalizando cuál es la categoría gramatical más probable para cada token. 
Como sabes, desde NLTK podemos acceder a corpus que ya están etiquetados. Vamos a utilizar alguno de los que ya conoces, el corpus de Brown, para entrenar nuestros propios etiquetadores.
Para ello importamos el corpus de Brown y almacenamos en un par de variables las noticias de este corpus en su versión etiquetada morfológicamente y sin etiquetar.
End of explanation
# imprimimos la primera oración de las noticias de Brown
print(brown_sents[0]) # sin anotar
print(brown_tagged_sents[0]) # etiquetada morfológicamente
Explanation: Para comparar ambas versiones, podemos imprimir la primera oración de este corpus en su versión sin etiquetas (fíjate que se trata de una lista de tokens, sin más) y en su versión etiquetada (se trata de una lista de tuplas donde el primer elemento es el token y el segundo es la etiqueta morfológica).
End of explanation
defaultTagger = nltk.DefaultTagger('NN')
print(defaultTagger.tag(oracion1))
print(defaultTagger.tag(oracion2))
Explanation: Etiquetado morfológico automático
Etiquetador por defecto
NLTK nos da acceso a tipos de etiquetadores morfológicos. Veamos cómo utilizar algunos de ellos.
A la hora de enfrentarnos al etiquetado morfológico de un texto, podemos adoptar una estrategia sencilla consistente en etiquetar por defecto todas las palabras con la misma categoría gramatical. Con NLTK podemos utilizar un DefaultTagger que etiquete todos los tokens como sustantivo. Las categoría sustantivo singular (NN en el esquema de etiquetas de Treebank) suele ser la más frecuente. Veamos qué tal funciona.
End of explanation
defaultTagger.evaluate(brown_tagged_sents)
Explanation: Obviamente no funciona bien, pero ojo, en el ejemplo anterior con oracion1 hemos etiquetado correctamente 2 de 10 tokens. Si lo evaluamos con un corpus más grande, como el conjunto de oraciones de Brown que ya tenemos, obtenemos una precisión superior al 13%:
End of explanation
patrones = [
    (r'[Aa]m$', 'BEM'),               # irregular forms of 'to be' 
    (r'[Aa]re$', 'BER'),              #  
    (r'[Ii]s$', 'BEZ'),               #  
    (r'[Ww]as$', 'BEDZ'),             #  
    (r'[Ww]ere$', 'BED'),             #  
    (r'[Bb]een$', 'BEN'),             #  
    (r'[Hh]ave$', 'HV'),              # irregular forms of 'to be' 
    (r'[Hh]as$', 'HVZ'),              #  
    (r'[Hh]ad$', 'HVD'),              #
    (r'I$', 'PRP'),                  # personal pronouns
    (r'[Yy]ou$', 'PRP'),              # 
    (r'[Hh]e$', 'PRP'),               # 
    (r'[Ss]he$', 'PRP'),              # 
    (r'[Ii]t$', 'PRP'),               # 
    (r'[Tt]hey$', 'PRP'),             # 
    (r'[Aa]n?$', 'AT'),               # 
    (r'[Tt]he$', 'AT'),               # 
    (r'[Ww]h.+$', 'WP'),              # wh- pronoun
    (r'.*ing$', 'VBG'),               # gerunds
    (r'.*ed$', 'VBD'),                # simple past
    (r'.*es$', 'VBZ'),                # 3rd singular present
    (r'[Cc]an(not|n\'t)?$', 'MD'),    # modals
    (r'[Mm]ight$', 'MD'),             # 
    (r'[Mm]ay$', 'MD'),               # 
    (r'.+ould$', 'MD'),               # modals: could, should, would
    (r'.*ly$', 'RB'),                 # adverbs
    (r'.*\'s$', 'NN$'),               # possessive nouns
    (r'.*s$', 'NNS'),                 # plural nouns
    (r'-?[0-9]+(.[0-9]+)?$', 'CD'),   # cardinal numbers
    (r'^to$', 'TO'),                  # to 
    (r'^in$', 'IN'),                  # in prep
    (r'^[A-Z]+([a-z])*$', 'NNP'),     # proper nouns 
    (r'.*', 'NN')                     # nouns (default)
]
regexTagger = nltk.RegexpTagger(patrones)
print(regexTagger.tag('I was taking a sunbath in Alpedrete'.split()))
print(regexTagger.tag('She would have found 100 dollars in the bag'.split()))
print(regexTagger.tag('DSFdfdsfsd 1852 to dgdfgould fXXXdg in XXXfdg'.split()))
Explanation: el método .evaluate que podemos ejecutar con cualquier etiquetador si especificamos como argumento una colección de referencia que ya esté etiquetada, nos devuelve un número: la precisión. Esta precisión se calcula como el porcentaje de tokens correctamente etiquetados por el tagger, teniendo en cuenta el corpus especificado como referencia.
Obviamente, los resultados son malos. Probemos con otras opciones más sofisticadas.
Etiquetador basado en Expresiones Regulares
Las expresiones regulares consisten en un lenguaje formal que nos permite especificar cadenas de texto. Ya las hemos utilizado en ocasiones anteriores. Pues bien, ahora podemos probar a definir distintas categorías morfológicas a partir de patrones, al menos para fenómenos morfológicos regulares.
A continuación definimos la variable patrones como una lista de tuplas, cuyo primer elemento se corresponde con la expresion regular que queremos capturar y el segundo elemento como la categoría gramatical. Y a partir de estas expresiones regulares creamos un RegexpTagger.
End of explanation
regexTagger.evaluate(brown_tagged_sents)
Explanation: Cuando probamos a evaluarlo con un corpus de oraciones más grande, vemos que nuestra precisión sube por encima del 32%.
End of explanation
print(len(brown_tagged_sents))
print((len(brown_tagged_sents) * 90) / 100)
size = int(len(brown_tagged_sents) * 0.9) # asegúrate de que conviertes esto a entero 
corpusEntrenamiento = brown_tagged_sents[:size]
corpusTest = brown_tagged_sents[size:]
# como ves, estos corpus contienen oraciones diferentes
print(corpusEntrenamiento[0])
print(corpusTest[0])
Explanation: Etiquetado basado en ngramas
Antes hemos dicho que la función nltk.pos_tag tenía un etiquetador morfológico que funcionaba con información estadística. A continuación vamos a reproducir, a pequeña escala, el proceso de entrenamiento de un etiquetador morfológico basado en aprendizaje automático. 
En general, los sistemas de aprendizaje automático funcionan del siguiente modo:
Se etiqueta manualmente una colección de datos. Cuanto más grande y más representativa sea la colección, mejores datos podremos extraer.
El sistema de aprendizaje procesa la colección de entrenamiento y "aprende" a partir de los ejemplos observados. En el caso del etiquetado morfológico, el sistema calcula frecuencias y generaliza (de distintas maneras) cuáles son las categorías gramaticales más probables para cada palabra. 
Una vez que el sistema ha sido entrenado, se evalúa su rendimiento sobre datos no observados.
Nosotros tenemos un pequeño corpus de ejemplos etiquetados: las oraciones del corpus de Brown de la categoría "noticias". Lo primero que necesitamos hacer es separar nuestros corpus de entrenamiento y test. En este caso, vamos a reservar el primer 90% de las oraciones para el entrenamiento (serán los ejemplos observados a partir de los cuales nuestro etiquetador aprenderá) y vamos a dejar el 10% restante para comprobar qué tal funciona.
End of explanation
unigramTagger = nltk.UnigramTagger(corpusEntrenamiento)
print(unigramTagger.evaluate(corpusTest))
# ¿qué tal se etiquetan nuestras oraciones de ejemplo?
print(unigramTagger.tag(oracion1))
print(unigramTagger.tag(oracion2))
print(unigramTagger.tag(oracion3))
Explanation: A continuación vamos a crear un etiquetador basado en unigramas (secuencias de una palabra o palabras sueltas) a través de la clase nltk.UnigramTagger, proporcionando nuestro corpusEntrenamiento para que aprenda. Una vez entrenado, vamos a evaluar su rendimiento sobre corpusTest.
End of explanation
bigramTagger = nltk.BigramTagger(corpusEntrenamiento)
trigramTagger = nltk.TrigramTagger(corpusEntrenamiento)
# funciona fatal :-(
print(bigramTagger.tag(oracion2))
print(trigramTagger.tag(oracion2))
# aquí hago trampas, le pido que analice una oración que ya ha visto durante el entrenamiento
print(bigramTagger.tag(['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', 'Friday', 'an', 'investigation', 'of', 
"Atlanta's", 'recent', 'primary', 'election', 'produced', '``', 'no', 'evidence', "''", 'that', 'any', 'irregularities', 'took', 'place', '.']))
Explanation: Los etiquetadores basados en unigramas se construyen a partir del simple cálculo de una distribución de frecuencia para cada token (palabra) y asignan siempre la etiqueta morfológica más probable. En nuestro caso, esta estrategia funciona relativamente bien: el tagger supera el 81% de precisión. Sin embargo, esta aproximación presenta numerosos problemas a la hora de etiquetar palabras homógrafas (un mismo token funcionando con más de una categoría gramatical). Si probamos con nuestra oracion2, comprobamos que la segunda aparición de progress no es etiquetada correctamente.
Intuitivamente, podemos pensar que sabríamos distinguir ambas categorías si tuviéramos en cuenta algo del contexto de aparición de las palabras: progress es un sustantivo cuando aparece después del artículo the y es verbo cuando aparece tras un pronombre personal como I. Si en lugar de calcular frecuencias de unigramas, extendiéramos los cálculos a secuencias de dos o tres palabras, podríamos tener mejores resultados. Y precisamente por eso vamos a calcular distribuciones de frecuencias condicionales: asignaremos a cada token la categoría gramatical más frecuente teniendo en cuenta la categoría gramatical de la(s) palabra(s) inmediatamente anterior(es). 
Creamos un par de etiquetadores basado en bigramas (secuencias de dos palabras) o trigramas (secuencias de tres palabras) a través de las clases nltk.BigramTagger y nltk.TrigramTagger. Y los probamos con nuestra oracion2.
End of explanation
print(bigramTagger.evaluate(corpusTest))
print(trigramTagger.evaluate(corpusTest))
Explanation: Como se ve en los ejemplos, los resultados son desastrosos. La mayoría de los tokens se quedan sin etiqueta y se muestran como None.
Si los evaluamos con nuestra colección de test, vemos que apenas superan el 10% de precisión. Peores resultados que nuestro DefaultTagger.
End of explanation
unigramTagger = nltk.UnigramTagger(corpusEntrenamiento, backoff=regexTagger)
bigramTagger = nltk.BigramTagger(corpusEntrenamiento, backoff=unigramTagger)
trigramTagger = nltk.TrigramTagger(corpusEntrenamiento, backoff=bigramTagger)
print(trigramTagger.evaluate(corpusTest))
print(trigramTagger.tag(oracion1))
print(trigramTagger.tag(oracion2))
print(trigramTagger.tag(oracion3))
Explanation: ¿Por qué ocurre esto? La intuición no nos engaña, y es verdad que si calculamos distribuciones de frecuencia condicionales teniendo en cuenta secuencias de palabras más largas, nuestros datos serán más finos. Sin embargo, cuando consideramos secuencias de tokens más largos nos arriesgamos a que dichas secuencias no aparezcan como tales en el corpus de entrenamiento. 
En el ejemplo de oracion2, nuestro bigramTagger es incapaz de etiquetar la palabra progress porque no ha encontrado en el corpus de entrenamiento ni el bigrama (The, progress) ni (I, progress). Obviamente, nuestro trigramTagger tampoco ha encontrado los trigramas (INICIO_DE_ORACIÓN, The, progress) o (as, I, progress). Si esas secuencias no aparecen en el corpus de entrenamiento, no hay nada que aprender. 
Combinamos etiquetadores
En estos casos, la solución más satisfactoria consiste en combinar de manera incremental la potencia de todos nuestros etiquetadores. Vamos a crear nuevos taggers que utilicen otros como respaldo. 
Utilizaremos un tagger de trigramas que, cuando no tenga respuesta para etiquetar un determinado token, utilizará como respaldo el tagger de bigramas. A su vez, el tagger de bigramas tirará del de unigramas cuando no tenga respuesta. Por último, el de unigramas utilizará como respaldo el tagger de expresiones regulares que definimos antes. De esta manera aumentamos la precisión hasta casi el 87%.
End of explanation
# escribe tu código aquí
corpus = brown.tagged_sents()
size = int(len(corpus) * 0.9) 
training = corpus[:size] 
test = corpus[size:]
defaultTagger = nltk.DefaultTagger('NN')
unigramTagger = nltk.UnigramTagger(training, backoff=defaultTagger)
bigramTagger = nltk.BigramTagger(training, backoff=unigramTagger)
trigramTagger = nltk.TrigramTagger(training, backoff=bigramTagger)
print(trigramTagger.evaluate(test))
print(trigramTagger.tag(oracion2))
Explanation: Ejercicio en clase
Vamos a mejorar nuestro etiquetador extendiendo el corpus de entrenamiento. En lugar de usar solo los textos de noticias, vamos a utilizar el corpus de Brown completo.
End of explanation
<END_TASK>