{ "cells": [ { "cell_type": "markdown", "id": "3cdcf323", "metadata": {}, "source": [ "#
Taller de Aprendizaje Automático
\n", "##
Taller 2: Críticas de Cine
" ] }, { "cell_type": "markdown", "id": "5b7e637d", "metadata": {}, "source": [ "Esta actividad constituye un primer acercamiento al campo de aprendizaje automático conocido como NLP (Natural Language Processing). En particular, exploraremos el campo conocido como *sentiment analysis* que consiste en determinar el sentimiento subyacente en un texto. En este caso clasificaremos las críticas realizadas por los usuarios a las películas como positivas o negativas. \n", "\n", "## Objetivos Generales del Taller\n", "\n", " - Entender la importancia de la limpieza y el preprocesamiento de los datos en el *pipeline* de aprendizaje \n", " - Construir, a partir de documentos de texto, vectores de características que puedan ser utilizados por clasificadores. \n", " - Generar un *pipeline* que permita clasificar una crítica como positiva o negativa utilizando técnicas clásicas de procesamiento de lenguaje natural.\n", " \n" ] }, { "cell_type": "markdown", "id": "graphic-longitude", "metadata": { "id": "graphic-longitude" }, "source": [ "## Formas de trabajo" ] }, { "cell_type": "markdown", "id": "similar-surgery", "metadata": { "heading_collapsed": true, "id": "similar-surgery" }, "source": [ "#### Opción 1: Trabajar localmente" ] }, { "cell_type": "markdown", "id": "616868da", "metadata": { "heading_collapsed": true, "hidden": true, "id": "616868da" }, "source": [ "##### Descarga de datos disponibles en Kaggle" ] }, { "cell_type": "markdown", "id": "9a76c306", "metadata": { "hidden": true, "id": "9a76c306" }, "source": [ "Luego, para descargar el dataset de IMDb reviews:" ] }, { "cell_type": "code", "execution_count": null, "id": "a25f6679", "metadata": { "hidden": true, "id": "a25f6679", "outputId": "d70c8c1e-530a-45ef-e259-f3addee20c02", "scrolled": true }, "outputs": [], "source": [ "!kaggle datasets download -d lakshmi25npathi/imdb-dataset-of-50k-movie-reviews" ] }, { "cell_type": "markdown", "id": "e60a6af5", "metadata": { "hidden": true, "id": "e60a6af5" }, "source": [ "Descomprima el archivo descargado:" ] }, { "cell_type": "code", "execution_count": null, "id": "f5598637", "metadata": { "hidden": true }, "outputs": [], "source": [ "import shutil\n", "shutil.unpack_archive('./imdb-dataset-of-50k-movie-reviews.zip', './')" ] }, { "cell_type": "code", "execution_count": null, "id": "0c354714", "metadata": { "hidden": true, "id": "0c354714", "outputId": "61a66a02-6248-4c03-a6a6-b1eb711b2970" }, "outputs": [], "source": [ "#!unzip imdb-dataset-of-50k-movie-reviews.zip" ] }, { "cell_type": "markdown", "id": "efficient-thailand", "metadata": { "id": "efficient-thailand" }, "source": [ "#### Opción 2: Trabajar en *Colab*. " ] }, { "cell_type": "markdown", "id": "compound-criminal", "metadata": { "id": "compound-criminal" }, "source": [ "Se puede trabajar en Google Colab. Para ello es necesario contar con una cuenta de **google drive** y ejecutar un notebook almacenado en dicha cuenta. De lo contrario, no se conservarán los cambios realizados en la sesión. En caso de ya contar con una cuenta, se puede abrir el notebook y luego ir a `Archivo-->Guardar una copia en drive`. " ] }, { "cell_type": "markdown", "id": "e_rith_Skga5", "metadata": { "id": "e_rith_Skga5" }, "source": [ "La siguiente celda monta el disco personal del drive:" ] }, { "cell_type": "code", "execution_count": null, "id": "timely-power", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 21875, "status": "ok", "timestamp": 1645451536176, "user": { "displayName": "Emiliano Acevedo", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09255842080725499836" }, "user_tz": 180 }, "id": "timely-power", "outputId": "9b878f94-05b8-4598-baa4-08a3e132868d" }, "outputs": [], "source": [ "from google.colab import drive\n", "drive.mount('/content/drive')" ] }, { "cell_type": "markdown", "id": "9WC47WBdkeqj", "metadata": { "id": "9WC47WBdkeqj" }, "source": [ "A continuación, vaya a su cuenta de [Kaggle](https://www.kaggle.com/) (o cree una si aún no lo ha hecho), haga clic en el icono de perfil en la esquina superior derecha de la pantalla y seleccione \"Your Account\" en la lista desplegable. Luego, seleccione la viñeta \"Account\" y haga clic en \"Create new API token\". Entonces un archivo llamado kaggle.json se descargará automáticamente a su carpeta de descargas. Este archivo contiene sus credenciales de inicio de sesión para permitirle acceder a la API." ] }, { "cell_type": "markdown", "id": "changing-enhancement", "metadata": { "id": "changing-enhancement" }, "source": [ "La siguiente celda realiza la configuración necesaria para obtener datos desde la plataforma Kaggle. Le solicitará que suba el archivo kaggle.json descargado anteriormente." ] }, { "cell_type": "code", "execution_count": null, "id": "convinced-person", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 90, "resources": { "http://localhost:8080/nbextensions/google.colab/files.js": { "data": "Ly8gQ29weXJpZ2h0IDIwMTcgR29vZ2xlIExMQwovLwovLyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKLy8geW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgovLyBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKLy8KLy8gICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKLy8KLy8gVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQovLyBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAovLyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KLy8gU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAovLyBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCi8qKgogKiBAZmlsZW92ZXJ2aWV3IEhlbHBlcnMgZm9yIGdvb2dsZS5jb2xhYiBQeXRob24gbW9kdWxlLgogKi8KKGZ1bmN0aW9uKHNjb3BlKSB7CmZ1bmN0aW9uIHNwYW4odGV4dCwgc3R5bGVBdHRyaWJ1dGVzID0ge30pIHsKICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3BhbicpOwogIGVsZW1lbnQudGV4dENvbnRlbnQgPSB0ZXh0OwogIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKHN0eWxlQXR0cmlidXRlcykpIHsKICAgIGVsZW1lbnQuc3R5bGVba2V5XSA9IHN0eWxlQXR0cmlidXRlc1trZXldOwogIH0KICByZXR1cm4gZWxlbWVudDsKfQoKLy8gTWF4IG51bWJlciBvZiBieXRlcyB3aGljaCB3aWxsIGJlIHVwbG9hZGVkIGF0IGEgdGltZS4KY29uc3QgTUFYX1BBWUxPQURfU0laRSA9IDEwMCAqIDEwMjQ7CgpmdW5jdGlvbiBfdXBsb2FkRmlsZXMoaW5wdXRJZCwgb3V0cHV0SWQpIHsKICBjb25zdCBzdGVwcyA9IHVwbG9hZEZpbGVzU3RlcChpbnB1dElkLCBvdXRwdXRJZCk7CiAgY29uc3Qgb3V0cHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKG91dHB1dElkKTsKICAvLyBDYWNoZSBzdGVwcyBvbiB0aGUgb3V0cHV0RWxlbWVudCB0byBtYWtlIGl0IGF2YWlsYWJsZSBmb3IgdGhlIG5leHQgY2FsbAogIC8vIHRvIHVwbG9hZEZpbGVzQ29udGludWUgZnJvbSBQeXRob24uCiAgb3V0cHV0RWxlbWVudC5zdGVwcyA9IHN0ZXBzOwoKICByZXR1cm4gX3VwbG9hZEZpbGVzQ29udGludWUob3V0cHV0SWQpOwp9CgovLyBUaGlzIGlzIHJvdWdobHkgYW4gYXN5bmMgZ2VuZXJhdG9yIChub3Qgc3VwcG9ydGVkIGluIHRoZSBicm93c2VyIHlldCksCi8vIHdoZXJlIHRoZXJlIGFyZSBtdWx0aXBsZSBhc3luY2hyb25vdXMgc3RlcHMgYW5kIHRoZSBQeXRob24gc2lkZSBpcyBnb2luZwovLyB0byBwb2xsIGZvciBjb21wbGV0aW9uIG9mIGVhY2ggc3RlcC4KLy8gVGhpcyB1c2VzIGEgUHJvbWlzZSB0byBibG9jayB0aGUgcHl0aG9uIHNpZGUgb24gY29tcGxldGlvbiBvZiBlYWNoIHN0ZXAsCi8vIHRoZW4gcGFzc2VzIHRoZSByZXN1bHQgb2YgdGhlIHByZXZpb3VzIHN0ZXAgYXMgdGhlIGlucHV0IHRvIHRoZSBuZXh0IHN0ZXAuCmZ1bmN0aW9uIF91cGxvYWRGaWxlc0NvbnRpbnVlKG91dHB1dElkKSB7CiAgY29uc3Qgb3V0cHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKG91dHB1dElkKTsKICBjb25zdCBzdGVwcyA9IG91dHB1dEVsZW1lbnQuc3RlcHM7CgogIGNvbnN0IG5leHQgPSBzdGVwcy5uZXh0KG91dHB1dEVsZW1lbnQubGFzdFByb21pc2VWYWx1ZSk7CiAgcmV0dXJuIFByb21pc2UucmVzb2x2ZShuZXh0LnZhbHVlLnByb21pc2UpLnRoZW4oKHZhbHVlKSA9PiB7CiAgICAvLyBDYWNoZSB0aGUgbGFzdCBwcm9taXNlIHZhbHVlIHRvIG1ha2UgaXQgYXZhaWxhYmxlIHRvIHRoZSBuZXh0CiAgICAvLyBzdGVwIG9mIHRoZSBnZW5lcmF0b3IuCiAgICBvdXRwdXRFbGVtZW50Lmxhc3RQcm9taXNlVmFsdWUgPSB2YWx1ZTsKICAgIHJldHVybiBuZXh0LnZhbHVlLnJlc3BvbnNlOwogIH0pOwp9CgovKioKICogR2VuZXJhdG9yIGZ1bmN0aW9uIHdoaWNoIGlzIGNhbGxlZCBiZXR3ZWVuIGVhY2ggYXN5bmMgc3RlcCBvZiB0aGUgdXBsb2FkCiAqIHByb2Nlc3MuCiAqIEBwYXJhbSB7c3RyaW5nfSBpbnB1dElkIEVsZW1lbnQgSUQgb2YgdGhlIGlucHV0IGZpbGUgcGlja2VyIGVsZW1lbnQuCiAqIEBwYXJhbSB7c3RyaW5nfSBvdXRwdXRJZCBFbGVtZW50IElEIG9mIHRoZSBvdXRwdXQgZGlzcGxheS4KICogQHJldHVybiB7IUl0ZXJhYmxlPCFPYmplY3Q+fSBJdGVyYWJsZSBvZiBuZXh0IHN0ZXBzLgogKi8KZnVuY3Rpb24qIHVwbG9hZEZpbGVzU3RlcChpbnB1dElkLCBvdXRwdXRJZCkgewogIGNvbnN0IGlucHV0RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlucHV0SWQpOwogIGlucHV0RWxlbWVudC5kaXNhYmxlZCA9IGZhbHNlOwoKICBjb25zdCBvdXRwdXRFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQob3V0cHV0SWQpOwogIG91dHB1dEVsZW1lbnQuaW5uZXJIVE1MID0gJyc7CgogIGNvbnN0IHBpY2tlZFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgaW5wdXRFbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsIChlKSA9PiB7CiAgICAgIHJlc29sdmUoZS50YXJnZXQuZmlsZXMpOwogICAgfSk7CiAgfSk7CgogIGNvbnN0IGNhbmNlbCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2J1dHRvbicpOwogIGlucHV0RWxlbWVudC5wYXJlbnRFbGVtZW50LmFwcGVuZENoaWxkKGNhbmNlbCk7CiAgY2FuY2VsLnRleHRDb250ZW50ID0gJ0NhbmNlbCB1cGxvYWQnOwogIGNvbnN0IGNhbmNlbFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgY2FuY2VsLm9uY2xpY2sgPSAoKSA9PiB7CiAgICAgIHJlc29sdmUobnVsbCk7CiAgICB9OwogIH0pOwoKICAvLyBXYWl0IGZvciB0aGUgdXNlciB0byBwaWNrIHRoZSBmaWxlcy4KICBjb25zdCBmaWxlcyA9IHlpZWxkIHsKICAgIHByb21pc2U6IFByb21pc2UucmFjZShbcGlja2VkUHJvbWlzZSwgY2FuY2VsUHJvbWlzZV0pLAogICAgcmVzcG9uc2U6IHsKICAgICAgYWN0aW9uOiAnc3RhcnRpbmcnLAogICAgfQogIH07CgogIGNhbmNlbC5yZW1vdmUoKTsKCiAgLy8gRGlzYWJsZSB0aGUgaW5wdXQgZWxlbWVudCBzaW5jZSBmdXJ0aGVyIHBpY2tzIGFyZSBub3QgYWxsb3dlZC4KICBpbnB1dEVsZW1lbnQuZGlzYWJsZWQgPSB0cnVlOwoKICBpZiAoIWZpbGVzKSB7CiAgICByZXR1cm4gewogICAgICByZXNwb25zZTogewogICAgICAgIGFjdGlvbjogJ2NvbXBsZXRlJywKICAgICAgfQogICAgfTsKICB9CgogIGZvciAoY29uc3QgZmlsZSBvZiBmaWxlcykgewogICAgY29uc3QgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsaScpOwogICAgbGkuYXBwZW5kKHNwYW4oZmlsZS5uYW1lLCB7Zm9udFdlaWdodDogJ2JvbGQnfSkpOwogICAgbGkuYXBwZW5kKHNwYW4oCiAgICAgICAgYCgke2ZpbGUudHlwZSB8fCAnbi9hJ30pIC0gJHtmaWxlLnNpemV9IGJ5dGVzLCBgICsKICAgICAgICBgbGFzdCBtb2RpZmllZDogJHsKICAgICAgICAgICAgZmlsZS5sYXN0TW9kaWZpZWREYXRlID8gZmlsZS5sYXN0TW9kaWZpZWREYXRlLnRvTG9jYWxlRGF0ZVN0cmluZygpIDoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ24vYSd9IC0gYCkpOwogICAgY29uc3QgcGVyY2VudCA9IHNwYW4oJzAlIGRvbmUnKTsKICAgIGxpLmFwcGVuZENoaWxkKHBlcmNlbnQpOwoKICAgIG91dHB1dEVsZW1lbnQuYXBwZW5kQ2hpbGQobGkpOwoKICAgIGNvbnN0IGZpbGVEYXRhUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CiAgICAgIGNvbnN0IHJlYWRlciA9IG5ldyBGaWxlUmVhZGVyKCk7CiAgICAgIHJlYWRlci5vbmxvYWQgPSAoZSkgPT4gewogICAgICAgIHJlc29sdmUoZS50YXJnZXQucmVzdWx0KTsKICAgICAgfTsKICAgICAgcmVhZGVyLnJlYWRBc0FycmF5QnVmZmVyKGZpbGUpOwogICAgfSk7CiAgICAvLyBXYWl0IGZvciB0aGUgZGF0YSB0byBiZSByZWFkeS4KICAgIGxldCBmaWxlRGF0YSA9IHlpZWxkIHsKICAgICAgcHJvbWlzZTogZmlsZURhdGFQcm9taXNlLAogICAgICByZXNwb25zZTogewogICAgICAgIGFjdGlvbjogJ2NvbnRpbnVlJywKICAgICAgfQogICAgfTsKCiAgICAvLyBVc2UgYSBjaHVua2VkIHNlbmRpbmcgdG8gYXZvaWQgbWVzc2FnZSBzaXplIGxpbWl0cy4gU2VlIGIvNjIxMTU2NjAuCiAgICBsZXQgcG9zaXRpb24gPSAwOwogICAgZG8gewogICAgICBjb25zdCBsZW5ndGggPSBNYXRoLm1pbihmaWxlRGF0YS5ieXRlTGVuZ3RoIC0gcG9zaXRpb24sIE1BWF9QQVlMT0FEX1NJWkUpOwogICAgICBjb25zdCBjaHVuayA9IG5ldyBVaW50OEFycmF5KGZpbGVEYXRhLCBwb3NpdGlvbiwgbGVuZ3RoKTsKICAgICAgcG9zaXRpb24gKz0gbGVuZ3RoOwoKICAgICAgY29uc3QgYmFzZTY0ID0gYnRvYShTdHJpbmcuZnJvbUNoYXJDb2RlLmFwcGx5KG51bGwsIGNodW5rKSk7CiAgICAgIHlpZWxkIHsKICAgICAgICByZXNwb25zZTogewogICAgICAgICAgYWN0aW9uOiAnYXBwZW5kJywKICAgICAgICAgIGZpbGU6IGZpbGUubmFtZSwKICAgICAgICAgIGRhdGE6IGJhc2U2NCwKICAgICAgICB9LAogICAgICB9OwoKICAgICAgbGV0IHBlcmNlbnREb25lID0gZmlsZURhdGEuYnl0ZUxlbmd0aCA9PT0gMCA/CiAgICAgICAgICAxMDAgOgogICAgICAgICAgTWF0aC5yb3VuZCgocG9zaXRpb24gLyBmaWxlRGF0YS5ieXRlTGVuZ3RoKSAqIDEwMCk7CiAgICAgIHBlcmNlbnQudGV4dENvbnRlbnQgPSBgJHtwZXJjZW50RG9uZX0lIGRvbmVgOwoKICAgIH0gd2hpbGUgKHBvc2l0aW9uIDwgZmlsZURhdGEuYnl0ZUxlbmd0aCk7CiAgfQoKICAvLyBBbGwgZG9uZS4KICB5aWVsZCB7CiAgICByZXNwb25zZTogewogICAgICBhY3Rpb246ICdjb21wbGV0ZScsCiAgICB9CiAgfTsKfQoKc2NvcGUuZ29vZ2xlID0gc2NvcGUuZ29vZ2xlIHx8IHt9OwpzY29wZS5nb29nbGUuY29sYWIgPSBzY29wZS5nb29nbGUuY29sYWIgfHwge307CnNjb3BlLmdvb2dsZS5jb2xhYi5fZmlsZXMgPSB7CiAgX3VwbG9hZEZpbGVzLAogIF91cGxvYWRGaWxlc0NvbnRpbnVlLAp9Owp9KShzZWxmKTsK", "headers": [ [ "content-type", "application/javascript" ] ], "ok": true, "status": 200, "status_text": "" } } }, "executionInfo": { "elapsed": 31400, "status": "ok", "timestamp": 1645451632279, "user": { "displayName": "Emiliano Acevedo", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09255842080725499836" }, "user_tz": 180 }, "id": "convinced-person", "outputId": "59951021-dcb6-4622-e24e-062806a6ce7f" }, "outputs": [], "source": [ "import warnings\n", "warnings.filterwarnings('ignore')\n", "from google.colab import files\n", "\n", "# El siguiente archivo solicitado es para habilitar la API de Kaggle en el entorno que está trabajando.\n", "# Este archivo se descarga entrando a su perfíl de Kaggle, en la sección API, presionando donde dice: Create New API Token\n", "\n", "uploaded = files.upload()\n", "\n", "for fn in uploaded.keys():\n", " print('User uploaded file \"{name}\" with length {length} bytes'.format(\n", " name=fn, length=len(uploaded[fn])))\n", "\n", "#Then move kaggle.json into the folder where the API expects to find it.\n", "!mkdir -p ~/.kaggle/ && mv kaggle.json ~/.kaggle/ && chmod 600 ~/.kaggle/kaggle.json" ] }, { "cell_type": "markdown", "id": "fossil-australian", "metadata": { "id": "fossil-australian" }, "source": [ "Una vez guardado el *token* se pueden descargar los datos, en este caso se bajarán los datos del dataset IMDb reviews:" ] }, { "cell_type": "code", "execution_count": null, "id": "independent-eagle", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "executionInfo": { "elapsed": 2104, "status": "ok", "timestamp": 1645451663400, "user": { "displayName": "Emiliano Acevedo", "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s64", "userId": "09255842080725499836" }, "user_tz": 180 }, "id": "independent-eagle", "outputId": "bfe00bd3-81a5-487f-e60f-8b6e2ce1a189" }, "outputs": [], "source": [ "!kaggle datasets download -d lakshmi25npathi/imdb-dataset-of-50k-movie-reviews" ] }, { "cell_type": "code", "execution_count": null, "id": "f9299bb5", "metadata": {}, "outputs": [], "source": [ "!unzip imdb-dataset-of-50k-movie-reviews.zip" ] }, { "cell_type": "markdown", "id": "sudden-therapist", "metadata": { "id": "sudden-therapist" }, "source": [ "\n", " \n", "
\n", " Ejecutar en Google Colab\n", "
" ] }, { "cell_type": "markdown", "id": "c55fa687", "metadata": { "heading_collapsed": true }, "source": [ "## Paquetes a utilizar" ] }, { "cell_type": "markdown", "id": "9c9f70ec", "metadata": { "hidden": true }, "source": [ "En esta actividad se utilizarán algunas bibliotecas auxiliares que deberán ser instaladas. Ejecutar la siguiente celda hasta que se ejecute sin errores. En caso de error, se puede instalar el paquete faltante desde el notebook con el comando:\n", "\n", "`!pip install paquete_faltante`" ] }, { "cell_type": "code", "execution_count": null, "id": "f166004a", "metadata": { "hidden": true }, "outputs": [], "source": [ "#import comet_ml in the top of your file\n", "from comet_ml import Experiment\n", "import numpy as np\n", "import sklearn\n", "from sklearn.model_selection import cross_validate\n", "import pandas as pd\n", "from wordcloud import WordCloud\n", "from matplotlib import pyplot as plt\n", "import re # regular expressions library\n", "import nltk # natural language processing toolkit" ] }, { "cell_type": "markdown", "id": "c744a377", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 1 - Carga de datos" ] }, { "cell_type": "markdown", "id": "fc76eb07", "metadata": { "hidden": true }, "source": [ "Se trabajará con una base de datos de 50000 críticas obtenidas del sitio Internet Movie Database (IMDb), dicha base de datos fue presentada originalmente en el artículo [Learning Word Vectors for Sentiment Analysis, A. L. Maas, R. E. Daly, P. T. Pham, D.Huang, A. Y. Ng, and C. Potts, Proceedings of the 49th Annual Meeting of the Association for Computational Linguistics: Human Language Technologies, pages 142–150, Portland, Oregon, USA, Association for Computational Linguistics, June 2011](https://ai.stanford.edu/~ang/papers/acl11-WordVectorsSentimentAnalysis.pdf) y se encuentra disponible en [Kaggle](https://www.kaggle.com/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews). En esta actividad se trabajará con los datos disponibles en Kaggle. \n", "\n", "Cada una de las críticas fue clasificada como *positive* si el usuario la puntuó con seis o más estrellas y *negative* en caso contrario." ] }, { "cell_type": "markdown", "id": "2b09a645", "metadata": { "hidden": true }, "source": [ "#### Objetivos:\n", "\n", " - Descargar la base de datos y levantar los datos.\n", " - Ejecutar la función `show_word_clouds()` provista sobre los datos levantados. La función asume que los datos se cargaron en un DataFrame." ] }, { "cell_type": "code", "execution_count": null, "id": "56ea2c80", "metadata": { "hidden": true }, "outputs": [], "source": [ "def show_word_clouds(data, stopwords=None):\n", " \n", " '''\n", " Entrada:\n", " data: DataFrame de pandas con las críticas y su evaluación\n", " stopwords: palabras que por ser muy comunes y/o\n", " poco informativas no se tomarán en cuenta al realizar la nube\n", " '''\n", " \n", " pos=' '.join(map(str,data['review'][data['sentiment']=='positive']))\n", " neg=' '.join(map(str,data['review'][data['sentiment']=='negative']))\n", "\n", " positive_cloud = WordCloud(width = 800, height = 800, \n", " background_color ='black', \n", " stopwords = stopwords, \n", " min_font_size = 10).generate(pos) \n", " \n", " negative_cloud = WordCloud(width = 800, height = 800, \n", " background_color ='black', \n", " stopwords = stopwords, \n", " min_font_size = 10).generate(neg) \n", "\n", " plt.figure(figsize=(16,8))\n", " plt.subplot(1,2,1)\n", " plt.imshow(positive_cloud)\n", " plt.title('Sentimento positivo')\n", " plt.axis('off')\n", " plt.subplot(1,2,2)\n", " plt.imshow(negative_cloud)\n", " plt.title('Sentimento negativo')\n", " plt.axis('off')" ] }, { "cell_type": "code", "execution_count": null, "id": "953f7369", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "a391e820", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 2 - Limpieza del texto" ] }, { "cell_type": "markdown", "id": "d99c5c39", "metadata": { "hidden": true }, "source": [ "Las *nubes de palabras* de la parte anterior sugieren que puede ser buena idea realizar un preprocesamiento de los datos ya que, por ejemplo:\n", " - Se obsevan tags html entre las palabras mostradas \n", " - Hay palabras muy comunes que aparecen en ambas categorías \n", " - Algunas palabras no parecen relevantes " ] }, { "cell_type": "markdown", "id": "6f343267", "metadata": { "hidden": true }, "source": [ "Se realizará un preprocesamiento de los datos de forma de eliminar los **tags html**. Para ello se utilizará la [biblioteca](https://docs.python.org/3.7/library/re.html) de expresiones regulares de Python. La utilización de las expresiones regulares para este fin es un tanto [controversial](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) pero a los efectos de esta actividad es totalmente adecuado. \n", "\n", "Las expresiones regulares son una herramienta muy potente pero que requiere de cierto tiempo para familiarizarse con sus reglas. En este [link](https://developers.google.com/edu/python/regular-expressions) hay un buen tutorial de Google. En esta primera versión el preprocesamiento hará las siguientes tareas:\n", "- Se eliminan los **tags html** . \n", "- Se eliminan caracteres *non-words*. Se consideran *words* a las letras, los dígitos y la barra baja. \n", "- Se pasará el texto a minúsculas\n", " \n", "Como ejemplo se muestra la crítica del usuario número 50, antes y después de eliminar los **tags html**. " ] }, { "cell_type": "code", "execution_count": null, "id": "7ecb2d0c", "metadata": { "hidden": true }, "outputs": [], "source": [ "def simple_preprocessor(text):\n", " text = re.sub('<.*?>','',text) # se eliminan los tags html\n", " text = re.sub('[\\W]+', ' ', text) # se eliminan caracteres 'non-words' \n", " # Words characters are a letter or digit or underbar [a-zA-Z0-9_].)\n", " text = text.lower() #\n", " return text" ] }, { "cell_type": "code", "execution_count": null, "id": "35a7a47a", "metadata": { "hidden": true }, "outputs": [], "source": [ "review_50 = 'Return to the 36th Chamber is one of those classic Kung-Fu movies which Shaw produces back in the 70s and 80s, whose genre is equivalent to the spaghetti westerns of Hollywood, and the protagonist Gordon Liu, the counterpart to the western\\'s Clint Eastwood. Digitally remastered and a new print made for the Fantastic Film Fest, this is \"Presented in Shaw Scope\", just like the good old days.

This film is a simple story of good versus evil, told in 3 acts, which more or less sums up the narrative of martial arts films in that era.

Act One sets up the premise. Workers in a dye-mill of a small village are unhappy with their lot, having their wages cut by 20% by incoming manchu gangsters. They can\\'t do much about their exploitation because none of them are martial arts skilled to take on the gangsters, and their boss. At first they had a minor success in getting Liu to impersonate a highly skilled Shaolin monk (one of the best comedy sequences), but their rouse got exposed when they pushed the limit of credibility by impersonating one too many times.

Act Two shows the protagonist wanting to get back at the mob. However, without real martial arts, he embarks on a journey to Shaolin Temple, to try and infiltrate and learn martial arts on the sly. After some slapstick moments, he finally gets accepted by the abbot (whom he impersonated!) but is disappointed at the teaching methods - kinda like Mr Miyagi\\'s style in Karate Kid, but instead of painting fences, he gets to erect scaffoldings all around the temple. Nothing can keep a good man down, and he unwittingly builds strength, endurance and learns kung-fu the unorthodox way.

Act Three is where the fight fest begins. With cheesy sound effects, each obvious non-contact on film is given the maximum impact treatment. But it is rather refreshing watching the fight scenes here, with its wide angled shots to highlight clarity and detail between the sparring partners, and the use of slow-motion only to showcase stunts in different angles. You may find the speed of fights a tad too slow, with some pause in between moves, but with Yuen Wo Ping and his style being used ad-nausem in Hollywood flicks, they sure don\\'t make fight scenes like they used to! Return to the 36th chamber gets a repeat screening on Monday, so, if you\\'re game for a nostalgic trip down memory lane, what are you waiting for?'\n", "print(review_50)" ] }, { "cell_type": "code", "execution_count": null, "id": "aaae18eb", "metadata": { "hidden": true }, "outputs": [], "source": [ "review_50_after = simple_preprocessor(review_50)\n", "print(review_50_after)" ] }, { "cell_type": "markdown", "id": "89d62c7b", "metadata": { "hidden": true }, "source": [ "### Preprocesamiento básico\n", "\n", " - Preprocesar todas las revisiones\n", " - Verificar el correcto funcionamiento visualizando las *nubes de palabras* después de preprocesar." ] }, { "cell_type": "code", "execution_count": null, "id": "8a1e99df", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "188dd5e3", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 3 - De documentos de texto a matrices numéricas" ] }, { "cell_type": "markdown", "id": "7cf955e5", "metadata": { "heading_collapsed": true, "hidden": true }, "source": [ "### Tokens" ] }, { "cell_type": "markdown", "id": "8d657e0d", "metadata": { "hidden": true }, "source": [ "Al proceso de pasar de un texto a su separación en palabras se le llama *tokenization*. La forma más simple de hacerlo es separarlo utilizando como límites de separación los espacios en blanco. Por ejemplo:" ] }, { "cell_type": "code", "execution_count": null, "id": "1e377d62", "metadata": { "hidden": true }, "outputs": [], "source": [ "def simple_tokenizer(text):\n", " return text.split()" ] }, { "cell_type": "code", "execution_count": null, "id": "06a1d25d", "metadata": { "hidden": true }, "outputs": [], "source": [ "sample_text = 'the actor seem to enjoy acting in every act'\n", "print(simple_tokenizer(sample_text))" ] }, { "cell_type": "markdown", "id": "c7b87aca", "metadata": { "heading_collapsed": true, "hidden": true }, "source": [ "### Modelo Bag of Words" ] }, { "cell_type": "markdown", "id": "93f497e5", "metadata": { "hidden": true }, "source": [ "El modelo *bag-of-words* es una forma de transformar una secuencia de palabras en en un vector de características numérico que pueda ser utilizado por un clasificador. En una de sus versiones más utilizados el procedimiento consiste en:\n", "\n", "1. Crear un diccionario de palabras \n", "2. Construir el vector de características de cada documento con las veces que aparece cada palabra del diccionario en el documento. \n", "\n", "En general la cantidad de palabras en el diccionario va a ser mayor que la cantidad de palabras únicas en el texto por lo que la representación se dice que es *sparse*, mayormente ceros en el vector de características." ] }, { "cell_type": "markdown", "id": "b65224a4", "metadata": { "hidden": true }, "source": [ "Afortunadamente *sklearn* cuenta con el módulo [CountVectorizer](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html) que construye el modelo bag-of-words por nosotros. " ] }, { "cell_type": "code", "execution_count": null, "id": "4467096b", "metadata": { "hidden": true }, "outputs": [], "source": [ "from sklearn.feature_extraction.text import CountVectorizer\n", "corpus = ['This is the first document.',\n", " 'This document is the second document.',\n", " 'And this is the third one.',\n", " 'Is this the first document?']\n", "vectorizer = CountVectorizer()\n", "X = vectorizer.fit_transform(corpus)\n", "print(vectorizer.get_feature_names_out())" ] }, { "cell_type": "code", "execution_count": null, "id": "e6aafdc2", "metadata": { "hidden": true }, "outputs": [], "source": [ "print(X.toarray())" ] }, { "cell_type": "markdown", "id": "2f5ab9b5", "metadata": { "heading_collapsed": true, "hidden": true }, "source": [ "### Subconjuntos de Entrenamiento y Test" ] }, { "cell_type": "markdown", "id": "6b06dcd4", "metadata": { "hidden": true }, "source": [ "- Dividir el conjunto de datos disponibles de forma de utilizar las primeras 35000 muestras para entrenamiento y reservar el resto para test." ] }, { "cell_type": "code", "execution_count": null, "id": "da836cdf", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "10a9c952", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 4 - Un primer pipeline" ] }, { "cell_type": "markdown", "id": "137c73c5", "metadata": { "hidden": true }, "source": [ "- Construir un **pipeline** que:\n", " 1. Tome como entrada los datos luego de realizar una limpieza básica de los mismos.\n", " 2. Genere el modelo *bag-of-words*.\n", " 3. Realice una clasificación de los mismos utilizando el clasificador de *regresión logística*. \n", "- Evalúe su desempeño utilizando diferentes métricas, por ejemplo: *precision*, *recall*, *accuracy*. " ] }, { "cell_type": "code", "execution_count": null, "id": "83c777a8", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "0efed4cd", "metadata": { "heading_collapsed": true, "hidden": true }, "source": [ "### Registrado de Experimentos en Comet\n", "\n", "[Documentación](https://www.comet.ml/docs/python-sdk/Experiment/) de funciones para registrar los experimentos." ] }, { "cell_type": "code", "execution_count": null, "id": "f19b6a96", "metadata": { "hidden": true }, "outputs": [], "source": [ "def save_results(cmt_exp, cross_val_results):\n", " '''\n", " Entrada:\n", " cmt_exp: experimento comet\n", " cross_val_scores: scikit-learn cross validation results\n", " '''\n", "\n", " ### Registro de Métricas de Evaluación ###\n", " fit_time = cross_val_results['fit_time']\n", " cv_accuracy = cross_val_results['test_accuracy']\n", " cv_precision = cross_val_results['test_precision']\n", " cv_recall = cross_val_results['test_recall']\n", " \n", " ### Resultados para cada fold ###\n", " for k in range(fit_time.size):\n", " metrics_step = {\n", " 'Fit Time':fit_time[k],\n", " 'Accuracy':cv_accuracy[k],\n", " 'Precision':cv_precision[k],\n", " 'Recall':cv_recall[k],\n", " }\n", " cmt_exp.log_metrics(metrics_step,step=k)\n", "\n", " ### Registro de Gráficas ###\n", " plt.figure()\n", " plt.plot(cross_val_results['test_accuracy'],'*-')\n", " plt.xlabel('fold')\n", " plt.ylabel('Accuracy')\n", " plt.title('Cross validation accuracy')\n", " plt.grid()\n", " \n", " cmt_exp.log_figure(figure_name=\"CV_Acc\" ,figure=plt)\n", " \n", " return" ] }, { "cell_type": "code", "execution_count": null, "id": "c51fee51", "metadata": { "hidden": true }, "outputs": [], "source": [ "API_KEY = '' # Se puede encontrar en Settings(Arriba a la derecha en Comet)" ] }, { "cell_type": "code", "execution_count": null, "id": "62775993", "metadata": { "hidden": true }, "outputs": [], "source": [ "#Crear un experimento con tu API KEY\n", "exp = Experiment(api_key=API_KEY, \n", " project_name='movie_reviews', # Nombre del proyecto donde se registran los experimentos\n", " auto_param_logging=False) \n", "exp.set_name('Parte 4') # Nombre de este experimento\n", "exp.add_tags(['Básico']) # Tags" ] }, { "cell_type": "code", "execution_count": null, "id": "cbaad70d", "metadata": { "hidden": true }, "outputs": [], "source": [ "# basic_pipeline = \n", "basic_pipeline_scores = cross_validate(basic_pipeline, X_train, y_train, scoring=['accuracy', 'precision', 'recall'],return_estimator=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "586d87a7", "metadata": { "hidden": true, "scrolled": true }, "outputs": [], "source": [ "exp.log_parameters(basic_pipeline.get_params())\n", "exp.log_parameter(\"modelo\",\"basico\")\n", "save_results(exp,basic_pipeline_scores)\n", "exp.end()" ] }, { "cell_type": "markdown", "id": "9d7c8560", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 5 - Stop-words" ] }, { "cell_type": "markdown", "id": "d517a79f", "metadata": { "hidden": true }, "source": [ "*Stop-words* son aquellas palabras que son extremadamente comunes y que por tanto es poco probable que contengan información relevante para clasificar documentos. La biblioteca **NLTK** cuenta con un conjunto de 127 *stop-words* que pueden obtenerse de la siguiente manera:" ] }, { "cell_type": "code", "execution_count": null, "id": "a5cb0e3d", "metadata": { "hidden": true }, "outputs": [], "source": [ "nltk.download('stopwords')\n", "from nltk.corpus import stopwords\n", "stop_words = stopwords.words('english')\n", "print(stop_words)" ] }, { "cell_type": "markdown", "id": "91fd13c1", "metadata": { "hidden": true }, "source": [ "#### Ejercicio \n", "\n", "- Modificar el **pipeline** de la parte anterior de forma tal que no utilice las **stops-words** definidas por **nltk**." ] }, { "cell_type": "code", "execution_count": null, "id": "0749474f", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "5f6452cc", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 6 - Tf-Idf : Ponderando la relevancia de las palabras" ] }, { "cell_type": "markdown", "id": "0690cfee", "metadata": { "hidden": true }, "source": [ "La eliminación de **stop-words** es una forma de considerar que determinadas palabras no son relevantes para la tarea que se quiere realizar. Otra forma de hacerlo, algo más específica, es la técnica conocida como **tf-idf**, del inglés *term frequency - inverse document frequency*. La idea detrás del método es quitarle peso a las palabras que aparecen frecuentemente en los textos que se quieren analizar. Se define como el producto entre la frecuencia con que aparece una palabra en el texto que se quiere analizar y el inverso de la frecuencia con que aparece en todo el conjunto de datos disponible.\n", "\n", "$$\n", "\\textit{tf-id}(t,d)= \\textit{tf}(t,d) \\times \\textit{idf}(t,d)\n", "$$" ] }, { "cell_type": "markdown", "id": "842c6177", "metadata": { "hidden": true }, "source": [ "El primer término del producto $\\textit{tf}(t, d)$ es la cantidad de veces que la palabra *t* aparece en el documento *d* que se quiere analizar. El segundo término $\\textit{idf}(t, d)$ es el inverso de la frecuencia del documento que se calcula:\n", "\n", "$$\n", "\\textit{idf}(t,d)=\\log \\frac{n_d}{1+df(d,t)}\n", "$$" ] }, { "cell_type": "markdown", "id": "82ffd712", "metadata": { "hidden": true }, "source": [ "donde $n_d$ es el número total de documentos y $\\textit{df}(d, t)$ es el número de documentos que contienen la palabra $t$. Existen variantes en cuanto a la fórmula exacta que se utiliza en las distintas implementaciones. Por ejemplo, la adición de 1 en el denominador se realizar para evitar la división entre cero al analizar palabras que no aparecen en el conjunto de entrenamiento. El logaritmo se usa para no darle demasiado peso a los documentos menos frecuentes." ] }, { "cell_type": "markdown", "id": "98afbcee", "metadata": { "hidden": true }, "source": [ "#### Ejercicio:\n", "\n", " - Modificar el **pipeline** de forma de tomar en cuenta la frecuencia con que aparecen las palabras. Para ello utilizar la clase *TfidTransformer* de *scikit-learn*. " ] }, { "cell_type": "code", "execution_count": null, "id": "3477fb5a", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "a3001150", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 7 - N-gramas" ] }, { "cell_type": "markdown", "id": "20ea13d3", "metadata": { "hidden": true }, "source": [ "El modelo *bag-of-words* que se presentó anteriormente se conoce también como *unigrama* ya que cada elemento del diccionario se corresponde con una sola palabra. Una extensión de este modelo es considerar como un elemento a toda secuencia de *n-palabras* que aparezca en el texto, a esta representación se la conoce como *n-grama*. \n", "\n", "En el texto 'This is the first document', por ejemplo, los elementos que se generan en las distintas representaciones son los siguientes: \n", "- 1-grama: 'This', 'is', 'the', 'first', 'document'\n", "- 2-grama: 'This is', 'is the', 'the first', 'first document'" ] }, { "cell_type": "markdown", "id": "aecc52e7", "metadata": { "hidden": true }, "source": [ "#### Ejercicio:\n", "\n", " - Modificar el *pipeline* de la **Parte 4 - Un primer pipeline** de forma de generar un modelo *2-grama* o *bigrama*." ] }, { "cell_type": "code", "execution_count": null, "id": "ba8ae279", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "037a2664", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 8 - Generar un modelo" ] }, { "cell_type": "markdown", "id": "24949b1b", "metadata": { "hidden": true }, "source": [ "#### Ejercicio:\n", "\n", " - Generar el mejor modelo posible utilizando **solamente** los datos del conjunto de entrenamiento. Se podrá explorar posibles mejoras en cualquiera de las siguientes etapas:\n", "\n", " - Limpieza y preprocesamiento de los datos (*ej.* Contracciones *don't*$\\rightarrow$*do not*)\n", " - Lemmatization (ver final)(puede explorarse los distintos Stemmers de la biblioteca *nltk*)\n", " - Modelo n-gramas (*Sugerencia:* tomar en cuenta más de un n-grama a la vez, probar limitar el tamaño del diccionario `max_features`)\n", " - Parámetros óptimos del clasificador" ] }, { "cell_type": "code", "execution_count": null, "id": "902ca91a", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "77261c5e", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 9 - Entrega del modelo y evaluación con el conjunto reservado para test" ] }, { "cell_type": "markdown", "id": "06114b42", "metadata": { "hidden": true }, "source": [ "#### Ejercicio:\n", "\n", " - Guardar el mejor modelo encontrado\n", " - Evaluar el accuracy con el conjunto de test." ] }, { "cell_type": "code", "execution_count": null, "id": "2bf45c20", "metadata": { "hidden": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "88ea01d9", "metadata": { "heading_collapsed": true }, "source": [ "## Parte 10 - Lemmatization (Opcional)" ] }, { "cell_type": "markdown", "id": "5266fb3b", "metadata": { "hidden": true }, "source": [ "Al separar las palabras con espacios en blancos, palabras derivadas de una misma raíz toman diferentes codificaciones. Desde un punto de vista semántico es preferible que estas palabras se agrupen en su raíz común. Este procedimiento se conoce como *stemming*. Uno de los problemas que tienen los *stemmers* es que pueden crear palabras no existentes. La técnica conocida como *lemmatization* busca encontrar palabras raíces gramaticalmente correctas, llamadas *lemmas*. Estas técnicas son computacionalmente más complejas." ] }, { "cell_type": "code", "execution_count": null, "id": "af154101", "metadata": { "hidden": true }, "outputs": [], "source": [ "from nltk.stem.wordnet import WordNetLemmatizer\n", "nltk.download('wordnet')" ] }, { "cell_type": "code", "execution_count": null, "id": "c78b6bad", "metadata": { "hidden": true }, "outputs": [], "source": [ "lemmatizer = WordNetLemmatizer()\n", "sentence= \"good better bad worse great greatest\"\n", "sentence_words = simple_tokenizer(sentence)\n", "\n", "print(\"{0:20}{1:20}\".format(\"Word\",\"Lemma\"))\n", "for word in sentence_words:\n", " print (\"{0:20}{1:20}\".format(word,lemmatizer.lemmatize(word,pos=\"a\")))" ] }, { "cell_type": "markdown", "id": "c5e4d54a", "metadata": { "hidden": true }, "source": [ "#### Ejercicio:\n", "\n", " - Modificar el *pipeline* de la **Parte 4 - Un primer pipeline** de forma de generar un modelo que incluya *Lemmatization*." ] }, { "cell_type": "code", "execution_count": null, "id": "2ebf32dd", "metadata": { "hidden": true }, "outputs": [], "source": [] } ], "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.11.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 5 }