{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":16295,"databundleVersionId":1099992,"sourceType":"competition"},{"sourceId":17777,"databundleVersionId":869809,"sourceType":"competition"},{"sourceId":19018,"databundleVersionId":2703900,"sourceType":"competition"},{"sourceId":982,"sourceType":"datasetVersion","datasetId":483}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 📚 NLP (Natural Language Processing) with Python\n\n***\n\nNatural language processing (NLP) is a subfield of linguistics, computer science, and artificial intelligence concerned with the interactions between computers and human language, in particular how to program computers to process and analyze large amounts of natural language data.\n\nIn this article, we will discuss a higher-level overview of the basics of Natural Language Processing, which basically consists of combining machine learning techniques with text, and using math and statistics to get that text in a format that the machine learning algorithms can understand!\n\n# 📝 Agenda\n\n> 1. Representing text as numerical data\n> 2. Reading a text-based dataset into pandas\n> 3. Vectorizing our dataset\n> 4. Building and evaluating a model\n> 5. Comparing models\n> 6. Examining a model for further insight\n> 7. Practicing this workflow on another dataset\n> 8. Tuning the vectorizer (discussion)\n\n---\n\n# 📌 Notebook Goals\n> In this notebook we will discuss a higher level overview of the basics of Natural Language Processing, which basically consists of combining machine learning techniques with text, and using math and statistics to get that text in a format that the machine learning algorithms can understand!","metadata":{}},{"cell_type":"markdown","source":"El procesamiento del lenguaje natural (PNL) es un subcampo de la lingüística, la informática y la inteligencia artificial que se ocupa de las interacciones entre las computadoras y el lenguaje humano, en particular cómo programar computadoras para procesar y analizar grandes cantidades de datos del lenguaje natural.\n\nEn este artículo, discutiremos una descripción general de nivel superior de los conceptos básicos del procesamiento del lenguaje natural, que básicamente consiste en combinar técnicas de aprendizaje automático con texto y usar matemáticas y estadísticas para obtener ese texto en un formato que los algoritmos de aprendizaje automático puedan comprender. !\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n%matplotlib inline\nsns.set_style(\"whitegrid\")\nplt.style.use(\"fivethirtyeight\")","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:32:59.701023Z","iopub.execute_input":"2024-02-16T19:32:59.701497Z","iopub.status.idle":"2024-02-16T19:33:02.60519Z","shell.execute_reply.started":"2024-02-16T19:32:59.701459Z","shell.execute_reply":"2024-02-16T19:33:02.604312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Representando el texto como un dato tipo númerico","metadata":{}},{"cell_type":"code","source":"# example text for model training (SMS messages)\nsimple_train = ['call you tonight', 'Call me a cab', 'Please call me... PLEASE!']","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:02.607418Z","iopub.execute_input":"2024-02-16T19:33:02.608233Z","iopub.status.idle":"2024-02-16T19:33:02.614436Z","shell.execute_reply.started":"2024-02-16T19:33:02.608191Z","shell.execute_reply":"2024-02-16T19:33:02.613322Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"📌 Desde [scikit-learn documentation](http://scikit-learn.org/stable/modules/feature_extraction.html#text-feature-extraction):\n\n> El análisis de texto es un campo de aplicación importante para los algoritmos de aprendizaje automático. Sin embargo, los datos sin procesar, una secuencia de símbolos, no se pueden alimentar directamente a los algoritmos mismos, ya que la mayoría de ellos esperan **vectores de características numéricas con un tamaño fijo** en lugar de **documentos de texto sin procesar con longitud variable.**.\n\nVamos a usar [CountVectorizer](http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html) Para \"convertir texto en una matriz de recuentos de tokens\":","metadata":{}},{"cell_type":"code","source":"#Importamos y hacemos la instancia de la libreria CountVectorizer, sin pasarle parametros.\nfrom sklearn.feature_extraction.text import CountVectorizer\n\nvect = CountVectorizer()\n\n#Le decimos que aprenda el vocabulario que deseamos entrenar.\nvect.fit(simple_train)\n\n#Examina el vocabulario consumido\nvect.get_feature_names_out()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:02.620478Z","iopub.execute_input":"2024-02-16T19:33:02.621493Z","iopub.status.idle":"2024-02-16T19:33:02.944691Z","shell.execute_reply.started":"2024-02-16T19:33:02.621464Z","shell.execute_reply":"2024-02-16T19:33:02.943601Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Transformamos los datos de entrenamiento en un \"Documento en terminos de matriz\"\nsimple_train_dtm = vect.transform(simple_train)\nsimple_train_dtm","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:02.94658Z","iopub.execute_input":"2024-02-16T19:33:02.947377Z","iopub.status.idle":"2024-02-16T19:33:02.956006Z","shell.execute_reply.started":"2024-02-16T19:33:02.947339Z","shell.execute_reply":"2024-02-16T19:33:02.954907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Que es una matriz dispersa o una matriz densa.\nMatriz Dispersa: Se almacena cualquier tipo de dato que sea diferente de cero. Esta es mejor cuando la mayoría de números son ceros, ya que los optimiza.\nMatriz Densa: En la matriz densa se almacena cualquier tipo de dato, incluido los ceros. Esta es mejor para matrices pequeñas, donde la mayor parte de los elementos son distintos de cero.","metadata":{}},{"cell_type":"code","source":"# Convertimos la matriz dispersa en una matriz densa.\nsimple_train_dtm.toarray()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:02.957998Z","iopub.execute_input":"2024-02-16T19:33:02.958932Z","iopub.status.idle":"2024-02-16T19:33:02.972037Z","shell.execute_reply.started":"2024-02-16T19:33:02.95889Z","shell.execute_reply":"2024-02-16T19:33:02.970905Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# examina juntos el vocabulario y la matriz de términos del documento\npd.DataFrame(simple_train_dtm.toarray(), columns=vect.get_feature_names())","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:02.973992Z","iopub.execute_input":"2024-02-16T19:33:02.974806Z","iopub.status.idle":"2024-02-16T19:33:03.478471Z","shell.execute_reply.started":"2024-02-16T19:33:02.974765Z","shell.execute_reply":"2024-02-16T19:33:03.473686Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"📌 Desde [scikit-learn documentation](http://scikit-learn.org/stable/modules/feature_extraction.html#text-feature-extraction):\n\n> En este esquema, las características y muestras se definen de la siguiente manera:\n\n> - Cada frecuencia de aparición de token individual (normalizada o no) se trata como una **característica**.\n> - El vector de todas las frecuencias de tokens para un documento determinado se considera una **muestra** multivariada.\n\n> Por lo tanto, un **corpus de documentos** puede representarse mediante una matriz en la que **una fila por documento** y **una columna por token** (por ejemplo, palabra) aparecen en el corpus.\n\n> Llamamos **vectorización** al proceso general de convertir una colección de documentos de texto en vectores de características numéricas. Esta estrategia específica (tokenización, conteo y normalización) se denomina representación **Bolsa de palabras** o \"Bolsa de n-gramas\". Los documentos se describen mediante la aparición de palabras, ignorando por completo la información de posición relativa de las palabras en el documento.","metadata":{}},{"cell_type":"code","source":"#Revisamos el tipo de documento en terminos de matriz.\nprint(type(simple_train_dtm))\n\n# Se examina el contenido disperso en la matriz.\nprint(simple_train_dtm)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.479503Z","iopub.status.idle":"2024-02-16T19:33:03.480422Z","shell.execute_reply.started":"2024-02-16T19:33:03.480198Z","shell.execute_reply":"2024-02-16T19:33:03.480217Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"📌 Desde [scikit-learn documentation](http://scikit-learn.org/stable/modules/feature_extraction.html#text-feature-extraction):\n\n> Como la mayoría de los documentos suelen utilizar un subconjunto muy pequeño de las palabras utilizadas en el corpus, la matriz resultante tendrá **muchos valores de características que son ceros** (normalmente más del 99 % de ellos).\n\n> Por ejemplo, una colección de 10 000 documentos de texto cortos (como correos electrónicos) utilizará un vocabulario con un tamaño del orden de 100 000 palabras únicas en total, mientras que cada documento utilizará de 100 a 1000 palabras únicas individualmente.\n\n> Para poder **almacenar dicha matriz en la memoria** pero también para **acelerar las operaciones**, las implementaciones normalmente usarán una **representación dispersa** como las implementaciones disponibles en `scipy.sparse `paquete.","metadata":{}},{"cell_type":"code","source":"#Ejemplo para probar el modelo.\nsimple_test = [\"please don't call me\"]","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.481477Z","iopub.status.idle":"2024-02-16T19:33:03.482041Z","shell.execute_reply.started":"2024-02-16T19:33:03.481834Z","shell.execute_reply":"2024-02-16T19:33:03.48185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> Para **hacer una predicción**, la nueva observación debe tener las **mismas características que las observaciones de entrenamiento**, tanto en número como en significado.","metadata":{}},{"cell_type":"code","source":"# transformar datos de prueba en una matriz de términos de documento (usando vocabulario existente)\nsimple_test_dtm = vect.transform(simple_test)\nsimple_test_dtm.toarray()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.483046Z","iopub.status.idle":"2024-02-16T19:33:03.483584Z","shell.execute_reply.started":"2024-02-16T19:33:03.483405Z","shell.execute_reply":"2024-02-16T19:33:03.483421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# examinar juntos el vocabulario y la matriz de términos del documento\npd.DataFrame(simple_test_dtm.toarray(), columns=vect.get_feature_names_out())","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.484586Z","iopub.status.idle":"2024-02-16T19:33:03.485166Z","shell.execute_reply.started":"2024-02-16T19:33:03.484982Z","shell.execute_reply":"2024-02-16T19:33:03.484999Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 📋 **Resumen:**\n\n> - `vect.fit(train)` **aprende el vocabulario** de los datos de entrenamiento\n> - `vect.transform(train)` usa el **vocabulario ajustado** para construir una matriz de términos de documento a partir de los datos de entrenamiento\n> - `vect.transform(test)` usa el **vocabulario ajustado** para construir una matriz de términos de documento a partir de los datos de prueba (e **ignora tokens** que no ha visto antes)","metadata":{}},{"cell_type":"markdown","source":"# 💾 Leyendo el conjunto de datos basado en el input, utilizando pandas","metadata":{}},{"cell_type":"code","source":"# read file into pandas using a relative path\nsms = pd.read_csv(\"/kaggle/input/sms-spam-collection-dataset/spam.csv\", encoding='latin-1')\nsms.dropna(how=\"any\", inplace=True, axis=1)\nsms.columns = ['label', 'message']\n\nsms.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.486137Z","iopub.status.idle":"2024-02-16T19:33:03.486656Z","shell.execute_reply.started":"2024-02-16T19:33:03.486478Z","shell.execute_reply":"2024-02-16T19:33:03.486494Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🔍 Análisis de datos exploratorios (EDA)","metadata":{}},{"cell_type":"code","source":"sms.describe()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.487649Z","iopub.status.idle":"2024-02-16T19:33:03.488201Z","shell.execute_reply.started":"2024-02-16T19:33:03.488008Z","shell.execute_reply":"2024-02-16T19:33:03.488025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sms.groupby('label').describe()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.48948Z","iopub.status.idle":"2024-02-16T19:33:03.48988Z","shell.execute_reply.started":"2024-02-16T19:33:03.489675Z","shell.execute_reply":"2024-02-16T19:33:03.489691Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We have `4825` ham message and `747` spam message","metadata":{}},{"cell_type":"code","source":"#Convertimos la etiqueta en una variable de tipo númerico.\nsms['label_num'] = sms.label.map({'ham':0, 'spam':1})\nsms.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.49187Z","iopub.status.idle":"2024-02-16T19:33:03.492973Z","shell.execute_reply.started":"2024-02-16T19:33:03.492684Z","shell.execute_reply":"2024-02-16T19:33:03.492706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> A medida que continuamos nuestro análisis, queremos empezar a pensar en las funciones que vamos a utilizar. Esto va de la mano con la idea general de ingeniería de características. Cuanto mejor sea su conocimiento del dominio sobre los datos, mejor será su capacidad para diseñar más funciones a partir de ellos. La ingeniería de funciones es una parte muy importante de la detección de spam en general.","metadata":{}},{"cell_type":"code","source":"sms['message_len'] = sms.message.apply(len)\nsms.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.494284Z","iopub.status.idle":"2024-02-16T19:33:03.495181Z","shell.execute_reply.started":"2024-02-16T19:33:03.494919Z","shell.execute_reply":"2024-02-16T19:33:03.494941Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(12, 8))\n\nsms[sms.label=='ham'].message_len.plot(bins=35, kind='hist', color='blue', \n                                       label='Ham messages', alpha=0.6)\nsms[sms.label=='spam'].message_len.plot(kind='hist', color='red', \n                                       label='Spam messages', alpha=0.6)\nplt.legend()\nplt.xlabel(\"Message Length\")","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.496961Z","iopub.status.idle":"2024-02-16T19:33:03.497719Z","shell.execute_reply.started":"2024-02-16T19:33:03.497443Z","shell.execute_reply":"2024-02-16T19:33:03.497465Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> ¡Muy interesante! A través de EDA básico hemos podido descubrir una tendencia de que los mensajes de spam tienden a tener más caracteres.","metadata":{}},{"cell_type":"code","source":"sms[sms.label=='ham'].describe()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.499606Z","iopub.status.idle":"2024-02-16T19:33:03.500803Z","shell.execute_reply.started":"2024-02-16T19:33:03.500602Z","shell.execute_reply":"2024-02-16T19:33:03.50062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sms[sms.label=='spam'].describe()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.502661Z","iopub.status.idle":"2024-02-16T19:33:03.503095Z","shell.execute_reply.started":"2024-02-16T19:33:03.502884Z","shell.execute_reply":"2024-02-16T19:33:03.502904Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> ¡Guau! 910 caracteres, usemos enmascaramiento para encontrar este mensaje:","metadata":{}},{"cell_type":"code","source":"sms[sms.message_len == 910].message.iloc[0]","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.505166Z","iopub.status.idle":"2024-02-16T19:33:03.50557Z","shell.execute_reply.started":"2024-02-16T19:33:03.505371Z","shell.execute_reply":"2024-02-16T19:33:03.505387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📑 Preprocesamiento de texto\n\n> Nuestro principal problema con nuestros datos es que están todos en formato de texto (cadenas). Los algoritmos de clasificación que utilizamos habitualmente necesitan algún tipo de vector de características numéricas para realizar la tarea de clasificación. En realidad, existen muchos métodos para convertir un corpus a un formato vectorial. El más simple es el enfoque de \"bolsa de palabras\", donde cada palabra única en un texto estará representada por un número.\n\n\n> En esta sección convertiremos los mensajes sin formato (secuencia de caracteres) en vectores (secuencias de números).\n\n> Como primer paso, escribamos una función que divida un mensaje en sus palabras individuales y devuelva una lista. También eliminaremos palabras muy comunes ('el', 'a', etc.). Para ello aprovecharemos la biblioteca `NLTK`. Es prácticamente la biblioteca estándar de Python para procesar texto y tiene muchas funciones útiles. Aquí solo usaremos algunos de los básicos.\n\n> Creemos una función que procesará la cadena en la columna del mensaje, luego podemos usar **apply()** en pandas para procesar todo el texto en el DataFrame.\n\n>Primero eliminando la puntuación. Podemos aprovechar la biblioteca **string** incorporada de Python para obtener una lista rápida de toda la puntuación posible:","metadata":{}},{"cell_type":"code","source":"import string\nfrom nltk.corpus import stopwords\n\ndef text_process(mess):\n    \"\"\"\n    Takes in a string of text, then performs the following:\n    1. Remove all punctuation\n    2. Remove all stopwords\n    3. Returns a list of the cleaned text\n    \"\"\"\n    STOPWORDS = stopwords.words('english') + ['u', 'ü', 'ur', '4', '2', 'im', 'dont', 'doin', 'ure']\n    # Verifique los caracteres para ver si están en puntuación.\n    nopunc = [char for char in mess if char not in string.punctuation]\n\n    # Une los caracteres nuevamente para formar la cadena.\n    nopunc = ''.join(nopunc)\n    \n    # Elimina los vacíos que hayan en el arreglo.\n    return ' '.join([word for word in nopunc.split() if word.lower() not in STOPWORDS])","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.507549Z","iopub.status.idle":"2024-02-16T19:33:03.507994Z","shell.execute_reply.started":"2024-02-16T19:33:03.507774Z","shell.execute_reply":"2024-02-16T19:33:03.50779Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sms.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.510075Z","iopub.status.idle":"2024-02-16T19:33:03.510726Z","shell.execute_reply.started":"2024-02-16T19:33:03.510532Z","shell.execute_reply":"2024-02-16T19:33:03.510549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> Ahora \"tokenicemos\" estos mensajes. Tokenización es solo el término utilizado para describir el proceso de convertir cadenas de texto normales en una lista de tokens (palabras que realmente queremos).","metadata":{}},{"cell_type":"code","source":"sms['clean_msg'] = sms.message.apply(text_process)\n\nsms.head()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.511848Z","iopub.status.idle":"2024-02-16T19:33:03.512944Z","shell.execute_reply.started":"2024-02-16T19:33:03.512641Z","shell.execute_reply":"2024-02-16T19:33:03.512664Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"type(stopwords.words('english'))","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.514247Z","iopub.status.idle":"2024-02-16T19:33:03.515084Z","shell.execute_reply.started":"2024-02-16T19:33:03.514791Z","shell.execute_reply":"2024-02-16T19:33:03.514814Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from collections import Counter\n\nwords = sms[sms.label=='ham'].clean_msg.apply(lambda x: [word.lower() for word in x.split()])\nham_words = Counter()\n\nfor msg in words:\n    ham_words.update(msg)\n    \nprint(ham_words.most_common(50))","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.516523Z","iopub.status.idle":"2024-02-16T19:33:03.517347Z","shell.execute_reply.started":"2024-02-16T19:33:03.517076Z","shell.execute_reply":"2024-02-16T19:33:03.517098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"words = sms[sms.label=='spam'].clean_msg.apply(lambda x: [word.lower() for word in x.split()])\nspam_words = Counter()\n\nfor msg in words:\n    spam_words.update(msg)\n    \nprint(spam_words.most_common(50))","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.518905Z","iopub.status.idle":"2024-02-16T19:33:03.519312Z","shell.execute_reply.started":"2024-02-16T19:33:03.519085Z","shell.execute_reply":"2024-02-16T19:33:03.5191Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🧮 Vectorization\n\n> Actualmente, tenemos los mensajes como listas de tokens (también conocidos como [lemmas](http://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html)) y ahora Necesitamos convertir cada uno de esos mensajes en un vector con el que puedan trabajar los modelos de algoritmos de SciKit Learn.\n\n> Ahora convertiremos cada mensaje, representado como una lista de tokens (lemmas) arriba, en un vector que los modelos de aprendizaje automático puedan entender.\n\n> Lo haremos en tres pasos usando el modelo de bolsa de palabras:\n\n> 1. Cuente cuantas veces aparece una palabra en cada mensaje (Conocido como término frecuencia)\n> 2. Pesar los conteos, para que los tokens frecuentes tengan menor peso (frecuencia de documentos inversa)\n> 3. Normalice los vectores a una unidad de longitud, para abstraerlos de la longitud del texto original (norma L2)\n\n> Comencemos el primer paso:\n\n> Cada vector tendrá tantas dimensiones como palabras únicas haya en el corpus del SMS. Primero usaremos **CountVectorizer** de SciKit Learn. Este modelo convertirá una colección de documentos de texto en una matriz de recuentos de tokens.\n\n> Podemos imaginar esto como una matriz bidimensional. Donde la 1 dimensión es el vocabulario completo (1 fila por palabra) y la otra dimensión son los documentos reales, en este caso una columna por mensaje de texto.\n\n> Por ejemplo:\n\n<table border = “1“>\n<tr>\n<th></th> <th>Message 1</th> <th>Message 2</th> <th>...</th> <th>Message N</th> \n</tr>\n<tr>\n<td><b>Word 1 Count</b></td><td>0</td><td>1</td><td>...</td><td>0</td>\n</tr>\n<tr>\n<td><b>Word 2 Count</b></td><td>0</td><td>0</td><td>...</td><td>0</td>\n</tr>\n<tr>\n<td><b>...</b></td> <td>1</td><td>2</td><td>...</td><td>0</td>\n</tr>\n<tr>\n<td><b>Word N Count</b></td> <td>0</td><td>1</td><td>...</td><td>1</td>\n</tr>\n</table>\n\n\n> Dado que hay tantos mensajes, podemos esperar muchos recuentos de cero por la presencia de esa palabra en ese documento. Debido a esto, SciKit Learn generará una [Matriz dispersa] (https://en.wikipedia.org/wiki/Sparse_matrix).","metadata":{}},{"cell_type":"code","source":"# Separamos X y Y para convertirlos en conjuntos de prueba y entrenamiento\nfrom sklearn.model_selection import train_test_split\n\n# Definimos X y Y (a partir de los datos de SMS) para usar con COUNTVECTORIZER\nX = sms.clean_msg\ny = sms.label_num\nprint(X.shape)\nprint(y.shape)\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)\n\nprint(X_train.shape)\nprint(X_test.shape)\nprint(y_train.shape)\nprint(y_test.shape)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.520804Z","iopub.status.idle":"2024-02-16T19:33:03.521305Z","shell.execute_reply.started":"2024-02-16T19:33:03.52105Z","shell.execute_reply":"2024-02-16T19:33:03.521069Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> Hay muchos argumentos y parámetros que se pueden pasar al CountVectorizer. En este caso simplemente especificaremos que **analyzer** sea nuestra propia función previamente definida:","metadata":{}},{"cell_type":"code","source":"from sklearn.feature_extraction.text import CountVectorizer\n\n# instancia del vectorizer\nvect = CountVectorizer()\nvect.fit(X_train)\n\n# Le enseñamos el vocabulario de datos para entrenamiento que vamos a utilizar para crear el documento en terminos de matriz\nX_train_dtm = vect.transform(X_train)\n\n#Esto equivale a entrenar y transformar en un solo paso\nX_train_dtm = vect.fit_transform(X_train)\n\n# Examinamos el documento creado anteriormente.\nprint(type(X_train_dtm), X_train_dtm.shape)\n\n# Aplicamos el mismo paso que hicimos en el entrenamiento, solo que ahora en la prueba\nX_test_dtm = vect.transform(X_test)\nprint(type(X_test_dtm), X_test_dtm.shape)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.522574Z","iopub.status.idle":"2024-02-16T19:33:03.523085Z","shell.execute_reply.started":"2024-02-16T19:33:03.522806Z","shell.execute_reply":"2024-02-16T19:33:03.522826Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.feature_extraction.text import TfidfTransformer\n\ntfidf_transformer = TfidfTransformer()\ntfidf_transformer.fit(X_train_dtm)\ntfidf_transformer.transform(X_train_dtm)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.524535Z","iopub.status.idle":"2024-02-16T19:33:03.525053Z","shell.execute_reply.started":"2024-02-16T19:33:03.524768Z","shell.execute_reply":"2024-02-16T19:33:03.524788Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🤖 Construimos y evaluamos el modelo\n\n> Usaremos [multinomial Naive Bayes](http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.MultinomialNB.html):\n\n> El clasificador multinomial Naive Bayes es adecuado para la clasificación con **características discretas** (por ejemplo, recuento de palabras para la clasificación de texto). La distribución multinomial normalmente requiere recuentos de características enteras. Sin embargo, en la práctica, los recuentos fraccionarios como tf-idf también pueden funcionar.","metadata":{}},{"cell_type":"code","source":"# Importación e instancia del Multinomial del modelo de valles.\nfrom sklearn.naive_bayes import MultinomialNB\nnb = MultinomialNB()","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.526184Z","iopub.status.idle":"2024-02-16T19:33:03.526683Z","shell.execute_reply.started":"2024-02-16T19:33:03.526432Z","shell.execute_reply":"2024-02-16T19:33:03.526452Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Entrenamos el modelo usando el X_train_dtm, sincronizandolo con un comando de Ipython\n%time nb.fit(X_train_dtm, y_train)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.528967Z","iopub.status.idle":"2024-02-16T19:33:03.529488Z","shell.execute_reply.started":"2024-02-16T19:33:03.529221Z","shell.execute_reply":"2024-02-16T19:33:03.529242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn import metrics\n\n# Realizamos las predicciones con los datos de prueba de la variable X\ny_pred_class = nb.predict(X_test_dtm)\n\n# calculate accuracy of class predictions\n#Calculamos la precisión.\nprint(\"=======Accuracy Score===========\")\nprint(metrics.accuracy_score(y_test, y_pred_class))\n\n# Imprimimos la matriz de confusión\nprint(\"=======Confision Matrix===========\")\nmetrics.confusion_matrix(y_test, y_pred_class)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.530803Z","iopub.status.idle":"2024-02-16T19:33:03.531336Z","shell.execute_reply.started":"2024-02-16T19:33:03.531064Z","shell.execute_reply":"2024-02-16T19:33:03.531087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# imprimir el texto del mensaje para falsos positivos (clasificador incorrecto)\n# X_test[(y_pred_class==1) & (y_test==0)]\nX_test[y_pred_class > y_test]","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.532757Z","iopub.status.idle":"2024-02-16T19:33:03.533819Z","shell.execute_reply.started":"2024-02-16T19:33:03.533536Z","shell.execute_reply":"2024-02-16T19:33:03.53356Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# imprimir el texto del mensaje para falsos negativos (clasificador de spam incorrecto)\nX_test[y_pred_class < y_test]","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.535198Z","iopub.status.idle":"2024-02-16T19:33:03.535704Z","shell.execute_reply.started":"2024-02-16T19:33:03.535437Z","shell.execute_reply":"2024-02-16T19:33:03.535468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# example of false negative \nX_test[4949]","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.53698Z","iopub.status.idle":"2024-02-16T19:33:03.537404Z","shell.execute_reply.started":"2024-02-16T19:33:03.537216Z","shell.execute_reply":"2024-02-16T19:33:03.537233Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# calcular las probabilidades previstas para X_test_dtm (mal calibrado)\ny_pred_prob = nb.predict_proba(X_test_dtm)[:, 1]\ny_pred_prob","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.538628Z","iopub.status.idle":"2024-02-16T19:33:03.539156Z","shell.execute_reply.started":"2024-02-16T19:33:03.538893Z","shell.execute_reply":"2024-02-16T19:33:03.538918Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# calculate AUC\nmetrics.roc_auc_score(y_test, y_pred_prob)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.540591Z","iopub.status.idle":"2024-02-16T19:33:03.541743Z","shell.execute_reply.started":"2024-02-16T19:33:03.541456Z","shell.execute_reply":"2024-02-16T19:33:03.54148Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.feature_extraction.text import TfidfTransformer\nfrom sklearn.pipeline import Pipeline\n\npipe = Pipeline([('bow', CountVectorizer()), \n                 ('tfid', TfidfTransformer()),  \n                 ('model', MultinomialNB())])\n\npipe.fit(X_train, y_train)\ny_pred = pipe.predict(X_test)\n\n# Calculamos la precisión de las predicciones\nprint(\"=======Accuracy Score===========\")\nprint(metrics.accuracy_score(y_test, y_pred))\n\n\nprint(\"=======Confision Matrix===========\")\nmetrics.confusion_matrix(y_test, y_pred)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.543028Z","iopub.status.idle":"2024-02-16T19:33:03.54351Z","shell.execute_reply.started":"2024-02-16T19:33:03.543277Z","shell.execute_reply":"2024-02-16T19:33:03.543299Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📊 Comparando modelos\n\nCompararemos Naive Bayes multinomial con [regresión logística] (http://scikit-learn.org/stable/modules/linear_model.html#logistic-regression):\n\n> La regresión logística, a pesar de su nombre, es un **modelo lineal para clasificación** en lugar de regresión. La regresión logística también se conoce en la literatura como regresión logit, clasificación de máxima entropía (MaxEnt) o clasificador log-lineal. En este modelo, las probabilidades que describen los posibles resultados de un único ensayo se modelan mediante una función logística.","metadata":{}},{"cell_type":"code","source":"# import an instantiate a logistic regression model\nfrom sklearn.linear_model import LogisticRegression\n\nlogreg = LogisticRegression(solver='liblinear')\n\n# train the model using X_train_dtm\n%time logreg.fit(X_train_dtm, y_train)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.548597Z","iopub.status.idle":"2024-02-16T19:33:03.54915Z","shell.execute_reply.started":"2024-02-16T19:33:03.548877Z","shell.execute_reply":"2024-02-16T19:33:03.548901Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# make class predictions for X_test_dtm\ny_pred_class = logreg.predict(X_test_dtm)\n\n# calculate predicted probabilities for X_test_dtm (well calibrated)\ny_pred_prob = logreg.predict_proba(X_test_dtm)[:, 1]\ny_pred_prob","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.550589Z","iopub.status.idle":"2024-02-16T19:33:03.551126Z","shell.execute_reply.started":"2024-02-16T19:33:03.550827Z","shell.execute_reply":"2024-02-16T19:33:03.550849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# calculate accuracy of class predictions\nprint(\"=======Accuracy Score===========\")\nprint(metrics.accuracy_score(y_test, y_pred_class))\n\n# print the confusion matrix\nprint(\"=======Confision Matrix===========\")\nprint(metrics.confusion_matrix(y_test, y_pred_class))\n\n# calculate AUC\nprint(\"=======ROC AUC Score===========\")\nprint(metrics.roc_auc_score(y_test, y_pred_prob))","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.552558Z","iopub.status.idle":"2024-02-16T19:33:03.553088Z","shell.execute_reply.started":"2024-02-16T19:33:03.5528Z","shell.execute_reply":"2024-02-16T19:33:03.552821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🧮 Sintonizando el vectorizador\n\nHasta ahora, hemos estado usando los parámetros predeterminados de [CountVectorizer:](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html)","metadata":{}},{"cell_type":"code","source":"# show default parameters for CountVectorizer\nvect","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.554591Z","iopub.status.idle":"2024-02-16T19:33:03.555114Z","shell.execute_reply.started":"2024-02-16T19:33:03.554839Z","shell.execute_reply":"2024-02-16T19:33:03.554877Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> 📌 Sin embargo, vale la pena ajustar el vectorizador, ¡al igual que vale la pena ajustar un modelo! A continuación se muestran algunos parámetros que quizás desee ajustar:\n\n> - 📌 **stop_words**: cadena {'inglés'}, lista o Ninguno (predeterminado)\n     - Si es \"inglés\", se utiliza una lista integrada de palabras vacías para inglés.\n     - Si es una lista, se supone que esa lista contiene palabras vacías, todas las cuales se eliminarán de los tokens resultantes.\n     - Si no hay ninguno, no se utilizarán palabras vacías.","metadata":{}},{"cell_type":"code","source":"# eliminar palabras vacías en inglés\nvect = CountVectorizer(stop_words='english')","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.55652Z","iopub.status.idle":"2024-02-16T19:33:03.557034Z","shell.execute_reply.started":"2024-02-16T19:33:03.556758Z","shell.execute_reply":"2024-02-16T19:33:03.556777Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> - 📌 **ngram_range**: tuple (min_n, max_n), default=(1, 1)\n    - El límite inferior y superior del rango de valores n para diferentes n-gramas que se extraerán.\n     - Se utilizarán todos los valores de n tales que min_n <= n <= max_n.","metadata":{}},{"cell_type":"code","source":"# include 1-grams and 2-grams\nvect = CountVectorizer(ngram_range=(1, 2))","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.561081Z","iopub.status.idle":"2024-02-16T19:33:03.561632Z","shell.execute_reply.started":"2024-02-16T19:33:03.561347Z","shell.execute_reply":"2024-02-16T19:33:03.56137Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> - 📌 **max_df**: float in range [0.0, 1.0] or int, default=1.0\n    - Al desarrollar el vocabulario, ignore los términos que tengan una frecuencia de documento estrictamente superior al umbral dado (palabras vacías específicas del corpus).\n     - Si es flotante, el parámetro representa una proporción de documentos.\n     - Si es un número entero, el parámetro representa un recuento absoluto.","metadata":{}},{"cell_type":"code","source":"# ignoramos los términos que aparecen en más del 50% de los documentos\nvect = CountVectorizer(max_df=0.5)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.563635Z","iopub.status.idle":"2024-02-16T19:33:03.564179Z","shell.execute_reply.started":"2024-02-16T19:33:03.56391Z","shell.execute_reply":"2024-02-16T19:33:03.563934Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> - 📌 **min_df**: float in range [0.0, 1.0] or int, default=1\n    - Al desarrollar el vocabulario, ignore los términos que tengan una frecuencia de documento estrictamente inferior al umbral dado. (Este valor también se denomina \"límite\" en la literatura).\n     - Si es flotante, el parámetro representa una proporción de documentos.\n     - Si es un número entero, el parámetro representa un recuento absoluto.","metadata":{}},{"cell_type":"code","source":"# Conservamos únicamente los términos que aparecen en al menos 2 documentos.\nvect = CountVectorizer(min_df=2)","metadata":{"execution":{"iopub.status.busy":"2024-02-16T19:33:03.565433Z","iopub.status.idle":"2024-02-16T19:33:03.565968Z","shell.execute_reply.started":"2024-02-16T19:33:03.56569Z","shell.execute_reply":"2024-02-16T19:33:03.56571Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"> - 📌 **Guidelines for tuning CountVectorizer**:\n    - Utilice su conocimiento del problema y del texto, y su comprensión de los parámetros de ajuste, para ayudarle a decidir qué parámetros ajustar y cómo hacerlo.\n     - ¡Experimenta y deja que los datos te indiquen cuál es el mejor enfoque!","metadata":{}}]}