{ "cells": [ { "cell_type": "markdown", "source": [ "Normalización de texto\n", "======================" ], "metadata": { "id": "ZBvMvsCCvQQD" }, "id": "ZBvMvsCCvQQD" }, { "cell_type": "markdown", "source": [ "## Introducción" ], "metadata": { "id": "lZpSRECVvQQI" }, "id": "lZpSRECVvQQI" }, { "cell_type": "markdown", "source": [ "Cuando hablamos de entrenar un modelo de aprendizaje automático, en general ocupamos una porción de nuestro tiempo en preprocesar los datos para generar representaciones útiles y deshacernos de problemas especificos que podría exhibir nuestro conjunto de datos. En particular, para el procesamiento del lenguaje natural, sabemos que debemos representar nuestras palabras de forma vectorial utilizando un vocabulario. También sabemos que el tamaño del vocabulario es algo que deseamos manejar.\n", "\n", "Tipicamente, las siguientes técnicas se aplican para procesar el texto:\n", "\n", " - [Normalización (Canonización)](#Normalización-(Canonización))\n", " - [Stemming y Lemmatization](#Stemming-y-Lemmatization)\n", " - [Eliminación de stopwords](#Eliminación-de-stopwords)\n", " - [Tokenización](#Tokenización)" ], "metadata": { "id": "MzC1DRuuvQQJ" }, "id": "MzC1DRuuvQQJ" }, { "cell_type": "markdown", "source": [ "### Para ejecutar este notebook" ], "metadata": { "id": "8SC7B_bLvQQK" }, "id": "8SC7B_bLvQQK" }, { "cell_type": "markdown", "source": [ "Para ejecutar este notebook, instale las siguientes librerias:" ], "metadata": { "id": "R4P8cjfNvQQK" }, "id": "R4P8cjfNvQQK" }, { "cell_type": "code", "execution_count": null, "source": [ "!wget https://raw.githubusercontent.com/santiagxf/M72109/master/docs/nlp/preprocessing/Normalization.txt --quiet --no-clobber\n", "!pip install -r Normalization.txt\n", "!pip install unidecode spaCy" ], "outputs": [], "metadata": { "id": "Thh6cEhgvQQK" }, "id": "Thh6cEhgvQQK" }, { "cell_type": "markdown", "source": [ "## Normalización (canonización)" ], "metadata": { "id": "LMu4RMX6vQQL" }, "id": "LMu4RMX6vQQL" }, { "cell_type": "markdown", "source": [ "La normalización (canonización) de texto hace referencia al proceso por el cual transformamos el texto en una única forma canónica común. Normalizar el texto antes de almacenarlo o procesarlo permite liberarnos de preocupaciones posteriores, ya que se garantiza que la entrada sea consistente antes de que se realicen operaciones sobre el mismo. La normalización del texto, sin embargo, requiere saber qué tipo de texto se está normalizando y cómo se procesará posteriormente. Por lo tanto, no existe un procedimiento de normalización universal.\n", "\n", "A pesar de no existir un proceso univeral, algunas técnicas si son comunes, como por ejemplo eliminar caracteres no alfanuméricos o marcas diacríticas (acentos, dieresis) y sustitución de mayusculas por minúsculas. Otras tareas podrían ser más específicas como ser el tratamiento de direcciones URL o incluso algunas combinaciones de caracteres como ser los emojis, los hashtags, etc." ], "metadata": { "id": "86SXEt9mvQQM" }, "id": "86SXEt9mvQQM" }, { "cell_type": "markdown", "source": [ "### Implementación" ], "metadata": { "id": "80KaQaLqvQQN" }, "id": "80KaQaLqvQQN" }, { "cell_type": "markdown", "source": [ "Para aquellas tareas sencillas, podemos utilizar algunas funciones pre-existentes. Para tareas más especificas, la utilización de expresiones regulares pueden ser de gran utilidad. Las expresiones regulares nos permiten buscar patrones específicos dentro de los textos. Veamos algunas trasnformaciones de texto.\n", "\n", "> Los siguientes ejemplos utilizan tweets reales extraidos del conjunto de datos [Spanish Corpus of Tweets for Marketing](http://ceur-ws.org/Vol-2111/paper1.pdf)" ], "metadata": { "id": "81JrpbDovQQN" }, "id": "81JrpbDovQQN" }, { "cell_type": "code", "execution_count": 2, "source": [ "sample = \"Ecologistas en Acción valora positivamente la decisión de Carrefour España de dejar de vender panga… https://t.co/16RuHAeNhY\"\n", "print(sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Ecologistas en Acción valora positivamente la decisión de Carrefour España de dejar de vender panga… https://t.co/16RuHAeNhY\n" ] } ], "metadata": { "id": "RLpqZMEBvQQO", "outputId": "12fd5879-aa85-40db-8d7b-92d31a4dd110", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "RLpqZMEBvQQO" }, { "cell_type": "markdown", "source": [ "Convertir el texto en minusculas" ], "metadata": { "id": "MKBXBW95vQQP" }, "id": "MKBXBW95vQQP" }, { "cell_type": "code", "execution_count": 3, "source": [ "sample = sample.lower()\n", "print(sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ecologistas en acción valora positivamente la decisión de carrefour españa de dejar de vender panga… https://t.co/16ruhaenhy\n" ] } ], "metadata": { "id": "ST0qn9xuvQQP", "outputId": "2043cb5f-6aeb-4935-ebda-a7fd1868a2cc", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "ST0qn9xuvQQP" }, { "cell_type": "markdown", "source": [ "Marcas diacríticas" ], "metadata": { "id": "M23j2juvvQQP" }, "id": "M23j2juvvQQP" }, { "cell_type": "code", "execution_count": 4, "source": [ "import unidecode\n", "\n", "sample = unidecode.unidecode(sample)\n", "print(sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ecologistas en accion valora positivamente la decision de carrefour espana de dejar de vender panga... https://t.co/16ruhaenhy\n" ] } ], "metadata": { "id": "GNCkbJaxvQQQ", "outputId": "5d6dcc63-c0eb-40ec-b149-cd7f814e0310", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "GNCkbJaxvQQQ" }, { "cell_type": "markdown", "source": [ "Eliminación de caracteres especiales" ], "metadata": { "id": "1USHgjgvvQQQ" }, "id": "1USHgjgvvQQQ" }, { "cell_type": "code", "execution_count": 5, "source": [ "import re\n", "\n", "charsToKepp = r'[^a-zA-Z0-9\\s]'\n", "sample = re.sub(charsToKepp, '', sample)\n", "print(sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "ecologistas en accion valora positivamente la decision de carrefour espana de dejar de vender panga httpstco16ruhaenhy\n" ] } ], "metadata": { "id": "FN3qmRgkvQQQ", "outputId": "f36a31bc-8a62-4645-c521-bb8b43d9c99a", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "FN3qmRgkvQQQ" }, { "cell_type": "markdown", "source": [ "Es interesante revisar el ejemplo anterior, dado que el efecto que obtuvo eliminar los caracteres especiales no fué el más indicado. En este caso, quisieramos eliminar las URLs por completo en lugar de solamente los caracteres especiales que están dentro de ellas.\n", "\n", "> En general, deberiamos invertir el orden de la celda anterior con la celda siguiente (primero eliminar las URLs y luego los caracteres especiales." ], "metadata": { "id": "9NRLUVZNvQQQ" }, "id": "9NRLUVZNvQQQ" }, { "cell_type": "code", "execution_count": 34, "source": [ "import re\n", "\n", "urls_regex = re.compile(r'http\\S+')\n", "sample = [token for token in sample.split(' ') if not re.match(urls_regex, token)]\n", "print(' '.join(sample))" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Vaya estafa de Mercadona. Voy y compro salsa de soja, curry, comino y sal sería buena idea. #Kiev\n" ] } ], "metadata": { "id": "bK7aZ3glvQQR", "outputId": "5b43e10e-f670-4366-db06-d92938d6f4fa", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "bK7aZ3glvQQR" }, { "cell_type": "markdown", "source": [ "Como se puede ver, el procesamiento del texto a realizar dependerá mucho del contexto." ], "metadata": { "id": "ZdU52leovQQR" }, "id": "ZdU52leovQQR" }, { "cell_type": "markdown", "source": [ "## Stemming y Lemmatization" ], "metadata": { "id": "VjT_dNDpvQQR" }, "id": "VjT_dNDpvQQR" }, { "cell_type": "markdown", "source": [ "Existen palabras cuyo significado no cambia ya que estan atados a una palabra raiz que les da el significado:\n", "\n", "> Organizan, organiza, organizando, organizaron\n", "\n", "**Stemming y Lemmatization** son dos técnicas que generan la palabra raiz dada una palabra. La diferencia que hay entre estas técnicas es que **Lemmatization** utiliza reglas del lenguaje para extraer las palabras raiz y por lo tanto, el resultado son palabras que existen en el vocabulario. Por el contrario, **Stemming** utiliza heuristicas que truncan la palabra hasta su raiz invariable. El resultado son \"psudopalabras\" o mejor conocidos como tokens que no forman una palabra del lenguaje propiamente dicho. Esta técnica, como se puede intuir, es más rápida computacionalmente." ], "metadata": { "id": "cplHlxuivQQR" }, "id": "cplHlxuivQQR" }, { "cell_type": "markdown", "source": [ "### Stemming" ], "metadata": { "id": "Eld_B11zvQQS" }, "id": "Eld_B11zvQQS" }, { "cell_type": "markdown", "source": [ "Stemming (o en español `derivación`) es el proceso en el que estandarizamos las formas de las palabras a su raíz base independientemente de las inflexiones o cojugación en la que se encuentre." ], "metadata": { "id": "jf9HydyFvQQS" }, "id": "jf9HydyFvQQS" }, { "cell_type": "markdown", "source": [ "Para demostrar esta técnica utilizaremos la popular libreria de NLP `nltk`:" ], "metadata": { "id": "b8M9xtBavQQS" }, "id": "b8M9xtBavQQS" }, { "cell_type": "code", "execution_count": 7, "source": [ "from nltk import stem\n", "\n", "stemmer = stem.SnowballStemmer(language='spanish')" ], "outputs": [], "metadata": { "id": "pFL2_9x4vQQS" }, "id": "pFL2_9x4vQQS" }, { "cell_type": "code", "execution_count": 8, "source": [ "words = ['amigos', 'amigo', 'amiga', 'amistad' ]" ], "outputs": [], "metadata": { "id": "QAvYEjjgvQQS" }, "id": "QAvYEjjgvQQS" }, { "cell_type": "code", "execution_count": 9, "source": [ "[stemmer.stem(word) for word in words]" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['amig', 'amig', 'amig', 'amist']" ] }, "metadata": {}, "execution_count": 9 } ], "metadata": { "id": "EyjjK6ODvQQS", "outputId": "2104bace-1693-4c91-bf58-51b5e45b4813", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "EyjjK6ODvQQS" }, { "cell_type": "markdown", "source": [ "### Lemmatization" ], "metadata": { "id": "GRXqX7V5vQQT" }, "id": "GRXqX7V5vQQT" }, { "cell_type": "markdown", "source": [ "El proceso de `lemmatization` es similar al de `stemming` salvo que al no utilizar reglas del lenguaje para extraer las palabras raiz. Como consecuencia, el resultado es el vocablo raiz propiamente dicho." ], "metadata": { "id": "pGVbZRu4vQQT" }, "id": "pGVbZRu4vQQT" }, { "cell_type": "markdown", "source": [ "Para aplicar esta técnica utilizaremos la librería `spaCy`.\n", "\n", ">**Sobre la libreria spaCy:** Spacy es una libreria para NLP muy polupar actualmente ya que, al contrario de nltk, ofrece formas muy eficientes de hacer solo algunos tipos de operaciones. NLTK es una herramienta más general. Para instalar spaCy en español necesitaran ejecutar:\n", "\n", "```\n", "conda install -c spacy spacy\n", "python -m spacy download es_core_news_sm\n", "```\n", "\n", ">Si bien `ntlk` ofrece la opción de hacer Lemmatization, su soporte mayoritariamente es para ingles. La versión en español no es demasiado buena. Si les interesa probarla puede hacerlo a traves del metodo.\n", "\n", "```\n", "nltk.wordnet.lemas(\"palabra\", lang='spa')\n", "```" ], "metadata": { "id": "tL2ILyLpvQQT" }, "id": "tL2ILyLpvQQT" }, { "cell_type": "markdown", "source": [ "Cargamos el modelo en español e instanciamos el parser:" ], "metadata": { "id": "-fI0ErmqvQQT" }, "id": "-fI0ErmqvQQT" }, { "cell_type": "code", "execution_count": 10, "source": [ "!python -m spacy download es_core_news_sm" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Collecting es-core-news-sm==3.8.0\n", " Downloading https://github.com/explosion/spacy-models/releases/download/es_core_news_sm-3.8.0/es_core_news_sm-3.8.0-py3-none-any.whl (12.9 MB)\n", "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.9/12.9 MB\u001b[0m \u001b[31m97.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25hInstalling collected packages: es-core-news-sm\n", "Successfully installed es-core-news-sm-3.8.0\n", "\u001b[38;5;2m✔ Download and installation successful\u001b[0m\n", "You can now load the package via spacy.load('es_core_news_sm')\n", "\u001b[38;5;3m⚠ Restart to reload dependencies\u001b[0m\n", "If you are in a Jupyter or Colab notebook, you may need to restart Python in\n", "order to load all the package's dependencies. You can do this by selecting the\n", "'Restart kernel' or 'Restart runtime' option.\n" ] } ], "metadata": { "id": "gmoj4UsXvQQT", "colab": { "base_uri": "https://localhost:8080/" }, "outputId": "94191095-601c-41f6-e821-89f41e1bbb2d" }, "id": "gmoj4UsXvQQT" }, { "cell_type": "code", "execution_count": 11, "source": [ "import es_core_news_sm as spa\n", "parser = spa.load()" ], "outputs": [], "metadata": { "id": "nkvEDCZJvQQT" }, "id": "nkvEDCZJvQQT" }, { "cell_type": "markdown", "source": [ "Creamos una funcion que nos ayuden a simplificar el uso de este método:" ], "metadata": { "id": "GVRLHLqAvQQT" }, "id": "GVRLHLqAvQQT" }, { "cell_type": "code", "execution_count": 12, "source": [ "lemmatizer = lambda word : \" \".join([token.lemma_ for token in parser(word)])" ], "outputs": [], "metadata": { "id": "NTRf-mPxvQQU" }, "id": "NTRf-mPxvQQU" }, { "cell_type": "code", "execution_count": 13, "source": [ "words = ['amigos', 'amigo', 'amiga', 'amistad' ]" ], "outputs": [], "metadata": { "id": "1Nfrro0VvQQU" }, "id": "1Nfrro0VvQQU" }, { "cell_type": "code", "execution_count": 14, "source": [ "[lemmatizer(word) for word in words]" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['amigo', 'amigo', 'amiga', 'amistad']" ] }, "metadata": {}, "execution_count": 14 } ], "metadata": { "id": "o6J_PfzxvQQU", "outputId": "78a7b693-48ed-4e56-dbce-12f21f2115fb", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "o6J_PfzxvQQU" }, { "cell_type": "markdown", "source": [ "> **Nota:** La precisión de Lemmatization depende de la implementación. La de español no es demasiado buena. Notar también lo que sucede con la palabra \"amigo\": ¿Es el verbo amigar o el sustantivo amigo?" ], "metadata": { "id": "N3qSDBEbvQQV" }, "id": "N3qSDBEbvQQV" }, { "cell_type": "markdown", "source": [ "Adicionalmente, `spaCy` procesa el texto [tokenizándolo](#tokenización) en `tokens` y enriqueciendolos con anotaciones." ], "metadata": { "id": "kN98W6C-vQQV" }, "id": "kN98W6C-vQQV" }, { "cell_type": "code", "execution_count": 15, "source": [ "words_tagged = parser(' '.join(words))" ], "outputs": [], "metadata": { "id": "OF2N6BaqvQQV" }, "id": "OF2N6BaqvQQV" }, { "cell_type": "code", "execution_count": 16, "source": [ "for t in words_tagged:\n", " print(t.text+'/'+t.lemma_ + '/'+ t.pos_)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "amigos/amigo/NOUN\n", "amigo/amigo/ADJ\n", "amiga/amigo/ADJ\n", "amistad/amistad/NOUN\n" ] } ], "metadata": { "id": "OfI0bL4IvQQW", "outputId": "e6adf4d3-c6e5-42ed-d0cb-146473614826", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "OfI0bL4IvQQW" }, { "cell_type": "markdown", "source": [ "## Eliminación de stopwords" ], "metadata": { "id": "v3O5yCZOvQQW" }, "id": "v3O5yCZOvQQW" }, { "cell_type": "markdown", "source": [ "Algunas palabras que son extremadamente frecuentes, \"a-priori\" (revisaremos este concepto luego) no son de mucha utilidad para resolver una tarea de clasificación de texto específica. Estas palabras se las conoce como Stop words y, dado que son de poca utilidad, son eliminadas del texto.\n", "\n", "> **Spoiler Alert:** Mencionamos 'a priori', porque la tendencia general en los ultimos tiempos ha sido ir desde grandes listas de stop words en el order de 200-300 a listas muy pequeñas (10-15 - si es que las hay). Los buscadores, por ejemplo, hoy en día no eliminan estas palabras. Cuando veamos modelos de lenguaje, en realidad las vamos a necesitar." ], "metadata": { "id": "1ScESY7GvQQW" }, "id": "1ScESY7GvQQW" }, { "cell_type": "markdown", "source": [ "Una de las formas más sencillas de eliminar estas palabras es utilizando la libreria `nltk` de la siguiente forma:" ], "metadata": { "id": "05gRzXiZvQQX" }, "id": "05gRzXiZvQQX" }, { "cell_type": "code", "execution_count": 17, "source": [ "import nltk\n", "from nltk.corpus import stopwords" ], "outputs": [], "metadata": { "id": "QA_z9zLevQQX" }, "id": "QA_z9zLevQQX" }, { "cell_type": "code", "execution_count": 18, "source": [ "nltk.download('stopwords', quiet=True)" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "True" ] }, "metadata": {}, "execution_count": 18 } ], "metadata": { "id": "uedHyoGMvQQX", "outputId": "4d5fcce7-570d-41cd-bd31-759a50da1fb2", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "uedHyoGMvQQX" }, { "cell_type": "code", "execution_count": 19, "source": [ "spa_stopwords = stopwords.words('spanish')" ], "outputs": [], "metadata": { "id": "ccBiOQ8FvQQX" }, "id": "ccBiOQ8FvQQX" }, { "cell_type": "markdown", "source": [ "Revisemos como lucen estas palabras:" ], "metadata": { "id": "mJwDPu6gvQQX" }, "id": "mJwDPu6gvQQX" }, { "cell_type": "code", "execution_count": 20, "source": [ "spa_stopwords[:10]" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['de', 'la', 'que', 'el', 'en', 'y', 'a', 'los', 'del', 'se']" ] }, "metadata": {}, "execution_count": 20 } ], "metadata": { "id": "tjBRcURsvQQY", "outputId": "be4974fd-6b50-4af5-c085-8bc8a8070015", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "tjBRcURsvQQY" }, { "cell_type": "markdown", "source": [ "### Implementación" ], "metadata": { "id": "LJ_hu9V3vQQY" }, "id": "LJ_hu9V3vQQY" }, { "cell_type": "markdown", "source": [ "Podemos implementar facilmente una rútina que elimine estas palabras de un texto de la siguiente forma:" ], "metadata": { "id": "RvcxOHkNvQQY" }, "id": "RvcxOHkNvQQY" }, { "cell_type": "code", "execution_count": 21, "source": [ "sample = \"ecologistas en accion valora positivamente la decision de carrefour espana de dejar de vender panga\"\n", "print('Antes:', sample)\n", "\n", "sample = ' '.join([token for token in sample.split(' ') if token not in spa_stopwords])\n", "print('Despues:', sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Antes: ecologistas en accion valora positivamente la decision de carrefour espana de dejar de vender panga\n", "Despues: ecologistas accion valora positivamente decision carrefour espana dejar vender panga\n" ] } ], "metadata": { "id": "PR5AHnmPvQQY", "outputId": "70569728-2537-4ad6-fa35-36f781fbc0a1", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "PR5AHnmPvQQY" }, { "cell_type": "markdown", "source": [ "## Tokenización" ], "metadata": { "id": "6_RkeEZ_vQQY" }, "id": "6_RkeEZ_vQQY" }, { "cell_type": "markdown", "source": [ "Se refiere al proceso de generación de tokens basado en un texto. A alto nivel, se podría ver como la tarea de dividir oraciones en palabras. Un token se diferencia de una palabra en el hecho de que una palabra es una instancia de un token. Existen varias técnicas para separar una oración o texto en general en tokens:\n", "\n", "> Lectura recomendada: [Diferentes *tokenizers* disponibles en *nltk*](http://www.nltk.org/api/nltk.tokenize.html)" ], "metadata": { "id": "gsaL2E4QvQQY" }, "id": "gsaL2E4QvQQY" }, { "cell_type": "markdown", "source": [ "Tomemos un tweet de ejemplo:" ], "metadata": { "id": "XkZYFjIBvQQZ" }, "id": "XkZYFjIBvQQZ" }, { "cell_type": "code", "execution_count": 22, "source": [ "sample = \". @PoliciadeBurgos @PCivilBurgos @Aytoburgos Mismo peligro c/ Rio Viejo junto Mercadona Villimar\"\n", "print(sample)" ], "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ ". @PoliciadeBurgos @PCivilBurgos @Aytoburgos Mismo peligro c/ Rio Viejo junto Mercadona Villimar\n" ] } ], "metadata": { "id": "zIjzmswYvQQZ", "outputId": "1615d673-7fd3-4651-9d23-625160789b3c", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "zIjzmswYvQQZ" }, { "cell_type": "markdown", "source": [ "Instanciaremos un `tokenizer` del tipo `TreebankWordTokenizer`, uno de los más genéricos:" ], "metadata": { "id": "zkpWfpPgvQQZ" }, "id": "zkpWfpPgvQQZ" }, { "cell_type": "code", "execution_count": 23, "source": [ "from nltk.tokenize.treebank import TreebankWordTokenizer\n", "\n", "tokenizer = TreebankWordTokenizer()" ], "outputs": [], "metadata": { "id": "uWpauKZJvQQZ" }, "id": "uWpauKZJvQQZ" }, { "cell_type": "code", "execution_count": 24, "source": [ "tokenizer.tokenize(sample)" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['.',\n", " '@',\n", " 'PoliciadeBurgos',\n", " '@',\n", " 'PCivilBurgos',\n", " '@',\n", " 'Aytoburgos',\n", " 'Mismo',\n", " 'peligro',\n", " 'c/',\n", " 'Rio',\n", " 'Viejo',\n", " 'junto',\n", " 'Mercadona',\n", " 'Villimar']" ] }, "metadata": {}, "execution_count": 24 } ], "metadata": { "id": "AQmEGuZCvQQZ", "outputId": "4c4e1590-446c-409d-8548-d32650bb3925", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "AQmEGuZCvQQZ" }, { "cell_type": "markdown", "source": [ "Intentemos ahora con un `tokenizer` un poco más específico para procesar tweets:" ], "metadata": { "id": "ZJMLpowRvQQZ" }, "id": "ZJMLpowRvQQZ" }, { "cell_type": "code", "execution_count": 25, "source": [ "from nltk.tokenize.casual import TweetTokenizer\n", "\n", "tokenizer = TweetTokenizer()" ], "outputs": [], "metadata": { "id": "f-KBZw14vQQZ" }, "id": "f-KBZw14vQQZ" }, { "cell_type": "code", "execution_count": 26, "source": [ "tokenizer.tokenize(sample)" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['.',\n", " '@PoliciadeBurgos',\n", " '@PCivilBurgos',\n", " '@Aytoburgos',\n", " 'Mismo',\n", " 'peligro',\n", " 'c',\n", " '/',\n", " 'Rio',\n", " 'Viejo',\n", " 'junto',\n", " 'Mercadona',\n", " 'Villimar']" ] }, "metadata": {}, "execution_count": 26 } ], "metadata": { "id": "3WQ2GXC9vQQa", "outputId": "d4012c65-b91e-4dd4-c3f2-9f178a91ef59", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "3WQ2GXC9vQQa" }, { "cell_type": "markdown", "source": [ "> Notar como el tratamiento del arroba resulta distinto dependiendo del `tokenizer` que estamos utilizando." ], "metadata": { "id": "TE1Xss5-vQQa" }, "id": "TE1Xss5-vQQa" }, { "cell_type": "markdown", "source": [ "### Otra estrategia\n", "El problema de reducir las palabras a sus formatos raiz radica en que en general cada palabra (separada por espacios, puntos, etc) conforma un elemento en nuestro vocabulario y no queremos diferentes elementos de nuestro vocabulario que mapeen al mismo elemento o concepto. Si por el contrario utilizaramos otra estrategia para determinar nuestro vocabulario (o mejor dicho, cada elemento de nuestro vocabulario) entonces este problema quizás no existiría (o se volvería peor).\n", "\n", "Este tipo de técnicas por lo general intentan representar el vocabulario con \"sub-palabras\" o partes de las palabras como unidad. Un ejemplo de esto es SentencePiec or Byte pair encoding (BPE).\n", "\n", "El siguiente ejemplo muetra el caso de BPE, el cual es utilizado por modelos como GPT. Veremos estos encodings más adelante." ], "metadata": { "id": "PITQp4dEvQQa" }, "id": "PITQp4dEvQQa" }, { "cell_type": "code", "source": [ "import tiktoken\n", "enc = tiktoken.get_encoding(\"cl100k_base\")" ], "metadata": { "id": "DCTKD4oS_ylj" }, "id": "DCTKD4oS_ylj", "execution_count": 27, "outputs": [] }, { "cell_type": "code", "source": [ "[enc.decode([t]) for t in enc.encode(\". @PoliciadeBurgos @PCivilBurgos @Aytoburgos Mismo peligro c/ Rio Viejo junto Mercadona Villimar\")]" ], "metadata": { "id": "3rqPdEDXANga", "outputId": "e07f8ba6-af80-47b1-a7e3-715cd574238d", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "3rqPdEDXANga", "execution_count": 28, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['.',\n", " ' @',\n", " 'Pol',\n", " 'ici',\n", " 'ade',\n", " 'B',\n", " 'urg',\n", " 'os',\n", " ' @',\n", " 'PC',\n", " 'ivil',\n", " 'B',\n", " 'urg',\n", " 'os',\n", " ' @',\n", " 'A',\n", " 'yt',\n", " 'ob',\n", " 'urg',\n", " 'os',\n", " ' M',\n", " 'ismo',\n", " ' pel',\n", " 'ig',\n", " 'ro',\n", " ' c',\n", " '/',\n", " ' Rio',\n", " ' Vie',\n", " 'jo',\n", " ' junto',\n", " ' Merc',\n", " 'ad',\n", " 'ona',\n", " ' Vill',\n", " 'imar']" ] }, "metadata": {}, "execution_count": 28 } ] }, { "cell_type": "markdown", "source": [ "## Creando una rutina de preparación del texto" ], "metadata": { "id": "2sLMbiXKvQQa" }, "id": "2sLMbiXKvQQa" }, { "cell_type": "markdown", "source": [ "Idealmente, podemos empaquetar todos los pasos relevantes del preprocesamiento de texto en una rutina coherente y consolidada. Esto es importante no solo por cuestiones de practicidad, sino que también es relevante dado que en todos estos pasos **el orden en el que se ejecutan importa**. Una ejecución en un orden distinto al que se pensó o diseño podría lugar a perdida de información. Por ejemplo, ¿que pasaría si quisieramos procesar los *hashtags* de tweets de alguna manera si eliminaramos los caracteres especiales al principio?" ], "metadata": { "id": "DbZkgl74vQQa" }, "id": "DbZkgl74vQQa" }, { "cell_type": "markdown", "source": [ "Una rutina podría ser la siguiente:" ], "metadata": { "id": "6sCaDif3vQQa" }, "id": "6sCaDif3vQQa" }, { "cell_type": "code", "execution_count": 31, "source": [ "import unidecode\n", "import spacy\n", "import es_core_news_sm as spa\n", "import re\n", "from nltk import stem\n", "from nltk.corpus import stopwords\n", "from nltk.tokenize.casual import TweetTokenizer\n", "\n", "nltk.download('stopwords', quiet=True)\n", "\n", "parser = spa.load() # Cargamos el parser en español\n", "tokenizer = TweetTokenizer(strip_handles=True, reduce_len=True) # Creamos un tokenizer\n", "stemmer = stem.SnowballStemmer(language='spanish') # Creamos un steammer\n", "lemmatizer = lambda word : \" \".join([token.lemma_ for token in parser(word)]) # Creamos un lemmatizer\n", "stopwords = set(stopwords.words('spanish')) # Instanciamos las stopwords en español\n", "urls_regex = re.compile(r'http\\S+') # Usamos una expresion regular para encontrar las URLs\n", "\n", "def normalize(text):\n", " tokens = tokenizer.tokenize(text.lower()) # Tokenizamos el texto\n", " tokens = [token for token in tokens if not re.match(urls_regex, token)] # Eliminamos URLs\n", " tokens = [token for token in tokens if len(token) > 4] # Eliminamos palabras con menos de 4 letras\n", " tokens = [token for token in tokens if token not in stopwords] # Eliminamos stopwords\n", " tokens = [unidecode.unidecode(token) for token in tokens] # Quitamos acentos\n", " tokens = [lemmatizer(token) for token in tokens] # Aplicamos lematization\n", " return tokens" ], "outputs": [], "metadata": { "id": "mRbevW8gvQQa" }, "id": "mRbevW8gvQQa" }, { "cell_type": "markdown", "source": [ "Luego podemos aplicar esta rutina facilmente a nuevo texto:" ], "metadata": { "id": "PD5be_bKvQQb" }, "id": "PD5be_bKvQQb" }, { "cell_type": "code", "execution_count": 32, "source": [ "sample = \"Vaya estafa de Mercadona. Voy y compro salsa de soja, curry, comino y sal sería buena idea. #Kiev https://t.co/Wej37UxCAs\"" ], "outputs": [], "metadata": { "id": "P3j5pu1cvQQb" }, "id": "P3j5pu1cvQQb" }, { "cell_type": "code", "execution_count": 33, "source": [ "normalize(sample)" ], "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['estafa',\n", " 'mercadonar',\n", " 'compro',\n", " 'salsar',\n", " 'curry',\n", " 'comino',\n", " 'buen',\n", " '# kiev']" ] }, "metadata": {}, "execution_count": 33 } ], "metadata": { "id": "a-RaIpnqvQQb", "outputId": "e0af5f1d-f502-43a4-d69f-3bb49b40938e", "colab": { "base_uri": "https://localhost:8080/" } }, "id": "a-RaIpnqvQQb" }, { "cell_type": "markdown", "source": [ "> **Nota:** En el futuro - en este curso - utilizaremos esta rutina de preprocesamiento de texto empaquedata en una transformación de Scikit-Learn. Los conceptos que utilizaremos serán los mismos que se utilizon aquí pero nos ahorraremos tener que escribir este código de preprocesamiento todo el tiempo. En la práctica, las rutinas de preprocesamiento de texto suelen ser tediosas y por lo tanto se las trata de escribir una sola vez y luego empaquetarlas de alguna forma para reutilizarlas una y otra vez.\n" ], "metadata": { "id": "LkkcU1F8v3Qg" }, "id": "LkkcU1F8v3Qg" } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.11" }, "colab": { "provenance": [], "toc_visible": true } }, "nbformat": 4, "nbformat_minor": 5 }