{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Modelado / Modelling \n", "\n", "Un **modelo** es una representación simplificada de la realidad creada para un propósito concreto. Las hipótesis que asumimos, la selección de características relevantes, las restricciones y la trazabilidad de cómo se construye forman parte de ese modelo.\n", "\n", "Un ejemplo clásico es un **mapa**: una representación cartográfica que permite situar lugares de interés, calcular distancias o analizar relaciones topológicas entre puntos.\n", "\n", "\n", "\n", "Fuente imagen: [Wikipedia](https://es.wikipedia.org/wiki/Proyecci%C3%B3n_cartogr%C3%A1fica)\n", "\n", "\n", "En **ciencia de datos** distinguimos, de forma general:\n", "\n", "- **Modelos predictivos**: conjuntos de fórmulas o reglas que permiten estimar valores desconocidos (por ejemplo, predecir ventas futuras). \n", "- **Modelos descriptivos**: ayudan a descubrir patrones subyacentes o estructura en los datos (por ejemplo, segmentos de clientes).\n", "\n", "Los datos usados por el modelo representan hechos u observaciones de la realidad (o de otro modelo). Siguiendo el ejemplo del mapa, serían los puntos, contornos o bordes del territorio representado.\n", "\n", "\n", "\n", "\n", "> Fuente de ambas figuras [2]\n", "\n", "\n", "## El proceso de aprendizaje automático\n", "\n", "Podemos esquematizar el proceso de aprendizaje automático en varias etapas:\n", "\n", "1. **Preparación de datos** \n", " Carga, limpieza, transformación y división del conjunto de datos. \n", " - Incluye el análisis exploratorio de datos (EDA) y la selección de características.\n", "\n", "2. **Selección de la técnica** \n", " Elección del tipo de tarea (clasificación, regresión, clustering…), de los modelos candidatos y de las restricciones prácticas (tiempo, interpretabilidad, recursos, etc.).\n", "\n", "3. **Ajuste de hiperparámetros** \n", " Los hiperparámetros son configuraciones del modelo que no se aprenden directamente de los datos (por ejemplo, profundidad máxima de un árbol) y deben ajustarse manualmente o mediante búsqueda automática.\n", "\n", "4. **Evaluación del modelo** \n", " Pruebas iniciales, elección de métricas, ajustes iterativos y evaluación final sobre datos no vistos.\n", "\n", "\n", "### Selección de atributos o métricas adecuadas\n", "\n", "Dado un conjunto de muestras con muchas características, el reto es **elegir aquellas que realmente contribuyen al aprendizaje del modelo**.\n", "\n", "Supongamos el siguiente ejemplo de personas:\n", "\n", "\n", "Las características disponibles son:\n", "\n", "- Forma de la cabeza: cuadrada o circular. \n", "- Forma del cuerpo: rectangular o ovalada. \n", "- Color del cuerpo: negro o blanco. \n", "- Función de su compra: *yes/no*.\n", "\n", "Esta última característica es el atributo objetivo (*target*): lo que queremos predecir en futuros clientes.\n", "\n", "Para predecirlo, debemos preguntarnos qué atributos aportan información útil. Si solo usáramos, por ejemplo, el color del cuerpo, podríamos perder variabilidad importante del resto de la población y construir un modelo pobre.\n", "\n", "Más adelante retomaremos este problema de selección de atributos introduciendo los conceptos de **entropía** y **ganancia de información**.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Nuestra primera aplicación de machine learning (ML)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Instalación de librerías\n", "\n", "La librería de Python que implementa múltiples algoritmos de ML es **scikit-learn**\n", "\n", "- Scikit-learn: https://scikit-learn.org/stable/\n", "\n", "Trabajemos con las versiones de librerías usadas en Google Colab (09/12/25) como sistema de referencia.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos usar UV:" ] }, { "cell_type": "code", "execution_count": 113, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Adding `\u001b[36m07-machinelearning\u001b[39m` as member of workspace `\u001b[36m/Users/isaac/Projects/Subjects/TTAD_master\u001b[39m`\n", "Initialized project `\u001b[36m07-machinelearning\u001b[39m`\n" ] } ], "source": [ "!uv init" ] }, { "cell_type": "code", "execution_count": 114, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[2K\u001b[2mResolved \u001b[1m100 packages\u001b[0m \u001b[2min 280ms\u001b[0m\u001b[0m \u001b[0m\n", "\u001b[2mAudited \u001b[1m5 packages\u001b[0m \u001b[2min 0.31ms\u001b[0m\u001b[0m\n", "\u001b[2K\u001b[2mResolved \u001b[1m100 packages\u001b[0m \u001b[2min 18ms\u001b[0m\u001b[0m \u001b[0m\n", "\u001b[2mAudited \u001b[1m11 packages\u001b[0m \u001b[2min 0.04ms\u001b[0m\u001b[0m\n", "\u001b[2K\u001b[2mResolved \u001b[1m100 packages\u001b[0m \u001b[2min 14ms\u001b[0m\u001b[0m \u001b[0m\n", "\u001b[2mAudited \u001b[1m20 packages\u001b[0m \u001b[2min 0.05ms\u001b[0m\u001b[0m\n" ] } ], "source": [ "!uv add scikit-learn==1.6.1\n", "!uv add sklearn-pandas==2.2.0\n", "!uv add seaborn==0.13.2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Y si tuvieramos pip:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "!pip install scikit-learn==1.6.1\n", "!pip install sklearn-pandas==2.2.0\n", "!pip install seaborn==0.13.2\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Datos\n", "\n", "Vamos a utilizar el siguiente catálogo de datos: **Mushroom Data Set** http://archive.ics.uci.edu/ml/datasets/Mushroom\n", "\n", "**Disponible** en: \"data/mushrooms.csv\"" ] }, { "cell_type": "code", "execution_count": 115, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(8124, 23)\n", "----------------------------------------------------------------------------------------------------\n", "Index(['clase', 'forma del sombrero', 'superficie del sombrero',\n", " 'color del sombrero', 'magulladuras', 'olor', 'unión de las láminas',\n", " 'espaciamiento de las láminas', 'tamaño de las láminas',\n", " 'color de las láminas', 'forma del tallo', 'raíz del tallo',\n", " 'superficie del tallo por encima del anillo',\n", " 'superficie del tallo por debajo del anillo',\n", " 'color del tallo por encima del anillo',\n", " 'color del tallo por debajo del anillo', 'tipo de velo',\n", " 'color del velo', 'número de anillos', 'tipo de anillo',\n", " 'color de la impresión de esporas', 'población', 'hábitat'],\n", " dtype='object')\n", "----------------------------------------------------------------------------------------------------\n", " clase forma del sombrero superficie del sombrero color del sombrero \\\n", "0 p x s n \n", "1 e x s y \n", "2 e b s w \n", "\n", " magulladuras olor unión de las láminas espaciamiento de las láminas \\\n", "0 t p f c \n", "1 t a f c \n", "2 t l f c \n", "\n", " tamaño de las láminas color de las láminas ... \\\n", "0 n k ... \n", "1 b k ... \n", "2 b n ... \n", "\n", " superficie del tallo por debajo del anillo \\\n", "0 s \n", "1 s \n", "2 s \n", "\n", " color del tallo por encima del anillo color del tallo por debajo del anillo \\\n", "0 w w \n", "1 w w \n", "2 w w \n", "\n", " tipo de velo color del velo número de anillos tipo de anillo \\\n", "0 p w o p \n", "1 p w o p \n", "2 p w o p \n", "\n", " color de la impresión de esporas población hábitat \n", "0 k s u \n", "1 n n g \n", "2 n n m \n", "\n", "[3 rows x 23 columns]\n" ] } ], "source": [ "import pandas as pd\n", "import numpy as np\n", "\n", "df = pd.read_csv(\"data/mushrooms.csv\")\n", "\n", "# Por simplicidad, renombramos las columnas\n", "es_col = [\n", " \"clase\",\n", " \"forma del sombrero\",\n", " \"superficie del sombrero\",\n", " \"color del sombrero\",\n", " \"magulladuras\",\n", " \"olor\",\n", " \"unión de las láminas\",\n", " \"espaciamiento de las láminas\",\n", " \"tamaño de las láminas\",\n", " \"color de las láminas\",\n", " \"forma del tallo\",\n", " \"raíz del tallo\",\n", " \"superficie del tallo por encima del anillo\",\n", " \"superficie del tallo por debajo del anillo\",\n", " \"color del tallo por encima del anillo\",\n", " \"color del tallo por debajo del anillo\",\n", " \"tipo de velo\",\n", " \"color del velo\",\n", " \"número de anillos\",\n", " \"tipo de anillo\",\n", " \"color de la impresión de esporas\",\n", " \"población\",\n", " \"hábitat\"\n", "]\n", "df.columns = es_col\n", "\n", "print(df.shape)\n", "print(\"-\"*100)\n", "print(df.columns)\n", "print(\"-\"*100)\n", "print(df.head(3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Planteamiento del objetivo de ML\n", "\n", "¿Cuál es el objetivo que planteamos? ¿Qué valos nos interesa encontrar?\n", "\n", "En este caso, **queremos determinar la población según las características del hongo**\n", "\n", "Esta variable contiene los siguientes datos:\n", "- population: abundant=a, clustered=c, numerous=n, scattered=s, several=v, solitary=y\n" ] }, { "cell_type": "code", "execution_count": 116, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 s\n", "1 n\n", "2 n\n", "3 s\n", "4 a\n", "Name: población, dtype: object\n", "['a' 'c' 'n' 's' 'v' 'y'] [ 384 340 400 1248 4040 1712]\n" ] } ], "source": [ "# Podemos ver los valores de la variable objetivo\n", "print(df[\"población\"].head()) \n", "value,counts = np.unique(df[\"población\"], return_counts=True)\n", "print(value,counts)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ¿Qué tipo de poblema de ML es?\n", "- Supervisado o No Supervisado?\n", "- Clasificación, Regresión o Agrupamiento?\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Selección de características (feature selection) \n", "\n", "En este primer problema, usaremos todas las muestras. \n", "\n", "Tenemos que separar el dataset en dos partes: características y variable objetivo." ] }, { "cell_type": "code", "execution_count": 117, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(8124, 22)\n", "(8124,)\n" ] } ], "source": [ "df_y = df[\"población\"].copy()\n", "df_x = df.drop(labels=[\"población\"],axis=1).copy()\n", "\n", "print(df_x.shape)\n", "print(df_y.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Preparación de los datos de entreno (train) y datos de comprobación (test)\n", "\n", "Podemos hacerlo de múltiples maneras, y es crítico en series temporales. Hay métodos automaticos que nos separan las muestras como por ejemplo: ```train_test_split```\n" ] }, { "cell_type": "code", "execution_count": 118, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x_train: (6499, 22)\n", "y_train: (6499,)\n", "----------------------------------------------------------------------------------------------------\n", "x_test: (1625, 22)\n", "y_test: (1625,)\n" ] } ], "source": [ "from sklearn.model_selection import train_test_split\n", "\n", "x_train, x_test, y_train, y_test = train_test_split(df_x,df_y, test_size=0.2, random_state = 0)\n", "print(\"x_train:\", x_train.shape)\n", "print(\"y_train: \",y_train.shape)\n", "print(\"-\"*100)\n", "print(\"x_test: \",x_test.shape)\n", "print(\"y_test: \",y_test.shape)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Elección del algoritmo\n", "\n", "Existen múltiples algoritmos según el tipo de problema de ML. En este ejemplo, usaremos ```Máquinas de Vectores de Soporte (SVM)'''\n", "- https://scikit-learn.org/stable/modules/svm.html\n", "- https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "could not convert string to float: 'e'", "output_type": "error", "traceback": [ "\u001b[31m---------------------------------------------------------------------------\u001b[39m", "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", "\u001b[32m/var/folders/6j/7gfvt_29797dypw8t1wttblw0000gn/T/ipykernel_24248/3448651391.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m()\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m sklearn.svm \u001b[38;5;28;01mimport\u001b[39;00m SVC\n\u001b[32m 2\u001b[39m \n\u001b[32m 3\u001b[39m clf = SVC(C=\u001b[32m1.0\u001b[39m, kernel=\u001b[33m\"linear\"\u001b[39m, random_state=\u001b[32m0\u001b[39m) \u001b[38;5;66;03m#¿Que implica el kernel?\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m clf.fit(x_train, y_train) \u001b[38;5;66;03m# Alerta: Genera un error!!! ¿Por qué?\u001b[39;00m\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/base.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(estimator, *args, **kwargs)\u001b[39m\n\u001b[32m 1385\u001b[39m skip_parameter_validation=(\n\u001b[32m 1386\u001b[39m prefer_skip_nested_validation \u001b[38;5;28;01mor\u001b[39;00m global_skip_validation\n\u001b[32m 1387\u001b[39m )\n\u001b[32m 1388\u001b[39m ):\n\u001b[32m-> \u001b[39m\u001b[32m1389\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m fit_method(estimator, *args, **kwargs)\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/svm/_base.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(self, X, y, sample_weight)\u001b[39m\n\u001b[32m 193\u001b[39m \n\u001b[32m 194\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m callable(self.kernel):\n\u001b[32m 195\u001b[39m check_consistent_length(X, y)\n\u001b[32m 196\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m197\u001b[39m X, y = validate_data(\n\u001b[32m 198\u001b[39m self,\n\u001b[32m 199\u001b[39m X,\n\u001b[32m 200\u001b[39m y,\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/utils/validation.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(_estimator, X, y, reset, validate_separately, skip_check_array, **check_params)\u001b[39m\n\u001b[32m 2957\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"estimator\"\u001b[39m \u001b[38;5;28;01mnot\u001b[39;00m \u001b[38;5;28;01min\u001b[39;00m check_y_params:\n\u001b[32m 2958\u001b[39m check_y_params = {**default_check_params, **check_y_params}\n\u001b[32m 2959\u001b[39m y = check_array(y, input_name=\u001b[33m\"y\"\u001b[39m, **check_y_params)\n\u001b[32m 2960\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m2961\u001b[39m X, y = check_X_y(X, y, **check_params)\n\u001b[32m 2962\u001b[39m out = X, y\n\u001b[32m 2963\u001b[39m \n\u001b[32m 2964\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28;01mnot\u001b[39;00m no_val_X \u001b[38;5;28;01mand\u001b[39;00m check_params.get(\u001b[33m\"ensure_2d\"\u001b[39m, \u001b[38;5;28;01mTrue\u001b[39;00m):\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/utils/validation.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(X, y, accept_sparse, accept_large_sparse, dtype, order, copy, force_writeable, force_all_finite, ensure_all_finite, ensure_2d, allow_nd, multi_output, ensure_min_samples, ensure_min_features, y_numeric, estimator)\u001b[39m\n\u001b[32m 1366\u001b[39m )\n\u001b[32m 1367\u001b[39m \n\u001b[32m 1368\u001b[39m ensure_all_finite = _deprecate_force_all_finite(force_all_finite, ensure_all_finite)\n\u001b[32m 1369\u001b[39m \n\u001b[32m-> \u001b[39m\u001b[32m1370\u001b[39m X = check_array(\n\u001b[32m 1371\u001b[39m X,\n\u001b[32m 1372\u001b[39m accept_sparse=accept_sparse,\n\u001b[32m 1373\u001b[39m accept_large_sparse=accept_large_sparse,\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/utils/validation.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_writeable, force_all_finite, ensure_all_finite, ensure_non_negative, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)\u001b[39m\n\u001b[32m 1052\u001b[39m )\n\u001b[32m 1053\u001b[39m array = xp.astype(array, dtype, copy=\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[32m 1054\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1055\u001b[39m array = _asarray_with_order(array, order=order, dtype=dtype, xp=xp)\n\u001b[32m-> \u001b[39m\u001b[32m1056\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ComplexWarning \u001b[38;5;28;01mas\u001b[39;00m complex_warning:\n\u001b[32m 1057\u001b[39m raise ValueError(\n\u001b[32m 1058\u001b[39m \u001b[33m\"Complex data not supported\\n{}\\n\"\u001b[39m.format(array)\n\u001b[32m 1059\u001b[39m ) \u001b[38;5;28;01mfrom\u001b[39;00m complex_warning\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/sklearn/utils/_array_api.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(array, dtype, order, copy, xp, device)\u001b[39m\n\u001b[32m 835\u001b[39m \u001b[38;5;66;03m# Use NumPy API to support order\u001b[39;00m\n\u001b[32m 836\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m copy \u001b[38;5;28;01mis\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m 837\u001b[39m array = numpy.array(array, order=order, dtype=dtype)\n\u001b[32m 838\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m839\u001b[39m array = numpy.asarray(array, order=order, dtype=dtype)\n\u001b[32m 840\u001b[39m \n\u001b[32m 841\u001b[39m \u001b[38;5;66;03m# At this point array is a NumPy ndarray. We convert it to an array\u001b[39;00m\n\u001b[32m 842\u001b[39m \u001b[38;5;66;03m# container that is consistent with the input's namespace.\u001b[39;00m\n", "\u001b[32m~/Projects/Subjects/TTAD_master/.venv/lib/python3.12/site-packages/pandas/core/generic.py\u001b[39m in \u001b[36m?\u001b[39m\u001b[34m(self, dtype, copy)\u001b[39m\n\u001b[32m 2167\u001b[39m )\n\u001b[32m 2168\u001b[39m values = self._values\n\u001b[32m 2169\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m copy \u001b[38;5;28;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 2170\u001b[39m \u001b[38;5;66;03m# Note: branch avoids `copy=None` for NumPy 1.x support\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m2171\u001b[39m arr = np.asarray(values, dtype=dtype)\n\u001b[32m 2172\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 2173\u001b[39m arr = np.array(values, dtype=dtype, copy=copy)\n\u001b[32m 2174\u001b[39m \n", "\u001b[31mValueError\u001b[39m: could not convert string to float: 'e'" ] } ], "source": [ "from sklearn.svm import SVC\n", "\n", "clf = SVC(C=1.0, kernel=\"linear\", random_state=0) #¿Que implica el kernel?\n", "#clf.fit(x_train, y_train) # Alerta: Genera un error!!! ¿Por qué?\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " clase forma del sombrero superficie del sombrero\n", "0 1 5 2\n", "1 0 5 2\n", "2 0 0 2\n" ] } ], "source": [ "# El error proviene porque los SVM tan solo funcionan con variables continuas (números)\n", "# Algunos algoritmos como los árboles de decisión, si funcionan con variables categoricas\n", "# En nuestro caso, tenemos que transformar las variables categoricas a variables discretas\n", "\n", "# Existen múltiples maneras de hacerlo. En este caso, usaremos el ```LabelEncoder```\n", "# Basicamente, lo que hace es asignar un número a cada categoría. La gestión de esta asignación recae en este encoder.\n", "# La opción más correcta es usar One-Hot Encoding (Anexo A), pero dada su dificultad lo haremos de esta manera.\n", "from sklearn.preprocessing import LabelEncoder\n", "\n", "le = LabelEncoder() # lo creamos\n", "\n", "# Aplicamos el encoder a todas las columnas del dataset inicial!!! \n", "for i in df.columns:\n", " df[i] = le.fit_transform(df[i]) # lo aplicamos a cada columna, \n", "\n", "print(df.iloc[:3,:3]) #imprimimos un par de filas y columnas para ver el resultado\n", "\n", "# Por lo tanto, no nos quedará más remedio que crear de nuevo la partición del dataset con la variable objetivo y las características \n", "x_train, x_test, y_train, y_test = train_test_split(df.drop('población', axis=1),df['población'], test_size=0.2,random_state=0)" ] }, { "cell_type": "code", "execution_count": 127, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
SVC(kernel='linear', random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SVC(kernel='linear', random_state=0)" ] }, "execution_count": 127, "metadata": {}, "output_type": "execute_result" } ], "source": [ "clf.fit(x_train, y_train) # Ahora sí! Entrenamos el modelo: TRAIN" ] }, { "cell_type": "code", "execution_count": 128, "metadata": {}, "outputs": [], "source": [ "# Una vez entrenado, podemos usar el modelo para predecir sobre nuevos datos o sobre los datos de control\n", "y_pred = clf.predict(x_test) # predecimos sobre los valores reservados" ] }, { "cell_type": "code", "execution_count": 131, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[4 5 2 3 5]\n", "380 3\n", "3641 5\n", "273 2\n", "1029 0\n", "684 4\n", "Name: población, dtype: int64\n" ] } ], "source": [ "print(y_pred[0:5])\n", "print(y_test[0:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Métricas\n", "\n", "Los datos de comprobación o de test nos sirven para medir la bondad del algoritmo sobre valores objetivos que conocemos.\n", "\n", "Las métricas dependerán del tipo de algoritmo. En este caso, estamos aplicando un algoritmo de clasificación, por lo tanto, tenemos métricas como la precisión, recall, f1-score, etc." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " 0 0.44 0.58 0.50 65\n", " 1 0.67 1.00 0.81 62\n", " 2 0.48 0.33 0.39 93\n", " 3 0.45 0.42 0.44 237\n", " 4 0.79 0.63 0.70 827\n", " 5 0.48 0.70 0.57 341\n", "\n", " accuracy 0.61 1625\n", " macro avg 0.55 0.61 0.57 1625\n", "weighted avg 0.64 0.61 0.62 1625\n", "\n" ] } ], "source": [ "from sklearn.metrics import classification_report\n", "print(classification_report(y_test, y_pred)) #comprobamos el error" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cuando se obtienen los valores, el problema continua: ¿Qué interpretación podemos hacer? ¿Es un buen método?..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluación de un modelo de aprendizaje\n", "\n", "La evaluación tiene como propósito **validar** el modelo y **ganar confianza** en su uso.\n", "\n", "Podemos distinguir dos perspectivas:\n", "\n", "- **Cualitativa** \n", " ¿El modelo contribuye a los objetivos de negocio y a la toma de decisiones? \n", " ¿Es integrable en los sistemas existentes? \n", " ¿Encaja con el *stack* tecnológico y los requisitos legales/éticos?\n", "\n", "- **Cuantitativa** \n", " Se apoya en métricas: tanto de rendimiento computacional (tiempo, memoria, coste) como de desempeño del modelo (error, precisión, etc.).\n", "\n", "\n", "### Métricas\n", "\n", "Las métricas concretas dependen del tipo de problema. \n", "En problemas de **clasificación** y **regresión** se utilizan, entre otras, las siguientes.\n", "\n", "#### En clasificación\n", " \n", "\n", "\n", "> Source: NillsF blog\n", "\n", "- **Accuracy**: proporción de predicciones correctas sobre el total: $ \\frac{TP+TN}{Total}.$\n", "- **Precision**: proporción de predicciones positivas que son realmente positivas: $\\frac{TP}{Results} = \\frac{TP}{TP+FP}$\n", "- **Recall** (sensibilidad): proporción de positivos reales correctamente identificados: $\\frac{TP}{Predictive Results} = \\frac{TP}{TP+FN}$\n", "- **F1-score**: media armónica de precisión y *recall*: $\\frac{2}{recall^{-1}+precision^{-1}}$\n", "- **Área bajo la curva ROC (AUC)**: para clasificación binaria. \n", " La curva ROC compara la tasa de verdaderos positivos frente a la tasa de falsos positivos para distintos umbrales, y el AUC mide la capacidad del modelo para separar ambas clases.\n", " \n", "- **Matriz de confusión**: tabla que cruza valores reales con valores predichos: \n", "$$\\begin{equation}\n", "\\begin{pmatrix}\n", "TP & FN \\\\\n", "FP & TN \n", "\\end{pmatrix}\n", "\\end{equation}$$\n", "\n", "#### En regresión\n", "\n", "- **Mean Absolute Error (MAE)**: media de las diferencias absolutas entre predicciones y valores reales.\n", "\n", "- **Mean Squared Error (MSE)**: media de los cuadrados de las diferencias entre predicciones y valores reales (no es exactamente la desviación estándar, sino su cuadrado excepto por factores de normalización).\n", "\n", "- **Coeficiente de determinación \\(R^2\\)**: indica qué proporción de la variabilidad de la variable objetivo explica el modelo.\n", "\n", "- **\\(R^2\\) ajustado (Adjusted \\(R^2\\))**: versión de \\(R^2\\) que penaliza la inclusión de variables adicionales y es más adecuada para comparar modelos con distinto número de predictores.\n", "\n", "Muchas de estas métricas (y otras adicionales) están implementadas en la librería **scikit-learn**:\n", "- https://scikit-learn.org/stable/modules/model_evaluation.html\n", "\n", "> Nota: Iremos utilizando las principales métricas en las unidades siguientes.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para el caso anterior, podemos calcular algunas de estas métricas de la siguiente manera:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.6123076923076923" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn.metrics import accuracy_score\n", "\n", "accuracy_score(y_test, y_pred)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.6413835956221657" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn import metrics\n", "\n", "metrics.precision_score(y_test,y_pred,average=\"weighted\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Recall: 0.6123076923076923\n", "F1_score: 0.6153073145466426\n" ] } ], "source": [ "print(\"Recall: \",metrics.recall_score(y_test,y_pred,average=\"weighted\"))\n", "print(\"F1_score: \",metrics.f1_score(y_test,y_pred,average=\"weighted\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Rendimiento computacional\n", "\n", "Cada operación tiene un **coste computacional**: no solo tiempo de ejecución, sino también **memoria** para almacenar datos y valores intermedios.\n", "\n", "La capacidad de CPU/GPU suele medirse en operaciones por segundo, por ejemplo:\n", "\n", "- MIPS (millones de instrucciones por segundo) \n", "- MFLOPS / GFLOPS (millones / miles de millones de operaciones en coma flotante por segundo)\n", " \n", "Este rendimiento influye directamente en el **tiempo de entrenamiento** y de **predicción** de los modelos.\n", "\n", "El **lenguaje de programación**, su **compilador/intérprete** y las **librerías numéricas** condicionan cómo se aprovechan CPU y GPU: número de *cores* utilizados, organización de tareas, gestión de memoria, etc.\n", "\n", "\n", "La **complejidad del modelo**, el **tamaño de los datos** y su **representación** afectan al rendimiento y, por tanto, a su aplicabilidad en un entorno real, donde el tiempo de respuesta debe ser razonable. \n", "Ejemplo: un sistema de recomendación cuyos resultados se calculan dinámicamente al cargar una página web.\n", "\n", "- https://scikit-learn.org/stable/computing/computational_performance.html\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
SVC(kernel='linear', random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "SVC(kernel='linear', random_state=0)" ] }, "execution_count": 102, "metadata": {}, "output_type": "execute_result" } ], "source": [ "clf.fit(x_train, y_train) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Tiempo de entrenamiento: 0.48070470802485943 s\n", "Tiempo de predicción: 0.11679345811717212 s\n" ] } ], "source": [ "from time import perf_counter\n", "\n", "t0 = perf_counter()\n", "clf.fit(x_train, y_train) \n", "t1 = perf_counter()\n", "print(\"Tiempo de entrenamiento:\", t1 - t0, \"s\")\n", "\n", "t0 = perf_counter()\n", "y_pred = clf.predict(x_test)\n", "t1 = perf_counter()\n", "print(\"Tiempo de predicción:\", t1 - t0, \"s\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[2mResolved \u001b[1m99 packages\u001b[0m \u001b[2min 0.48ms\u001b[0m\u001b[0m\n", "\u001b[2mAudited \u001b[1m94 packages\u001b[0m \u001b[2min 0.01ms\u001b[0m\u001b[0m\n" ] } ], "source": [ "!uv add memory_profiler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Memoria máxima (MB): 242.875\n" ] } ], "source": [ "from memory_profiler import memory_usage\n", "\n", "def train():\n", " clf.fit(x_train, y_train)\n", " return clf\n", "\n", "mem_usage = memory_usage(train)\n", "print(\"Memoria máxima (MB):\", max(mem_usage))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La memoria no es un problema a priori que debe de preocuparos en este punto de vuestro aprendizaje. **El tiempo de entrenamiento y predicción, sí.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[![License: CC BY 4.0](https://img.shields.io/badge/License-CC_BY_4.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/)
\n", "Isaac Lera and Gabriel Moya
\n", "Universitat de les Illes Balears
\n", "isaac.lera@uib.edu, gabriel.moya@uib.edu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Anexo A\n", "\n", "`One-Hot Encoding' sería la técnica más idonea para transformar las categorías del dataset de mushrooms.\n", "\n", "- https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "----------------------------------------------------------------------------------------------------\n", "(6499, 113)\n", "----------------------------------------------------------------------------------------------------\n", "\n", " Coords\tValues\n", " (0, 0)\t1.0\n", " (1, 0)\t1.0\n", " (2, 0)\t1.0\n", "-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n", " precision recall f1-score support\n", "\n", " a 0.44 0.58 0.50 65\n", " c 0.68 0.87 0.76 62\n", " n 0.36 0.19 0.25 93\n", " s 0.43 0.63 0.51 237\n", " v 0.77 0.59 0.67 827\n", " y 0.45 0.56 0.50 341\n", "\n", " accuracy 0.58 1625\n", " macro avg 0.52 0.57 0.53 1625\n", "weighted avg 0.62 0.58 0.59 1625\n", "\n" ] } ], "source": [ "from sklearn.model_selection import train_test_split\n", "from sklearn.preprocessing import OneHotEncoder\n", "from sklearn.compose import ColumnTransformer\n", "from sklearn.svm import SVC\n", "from sklearn.metrics import classification_report\n", "import pandas as pd\n", "\n", "df = pd.read_csv(\"data/mushrooms.csv\")\n", "\n", "# Por simplicidad, renombramos las columnas\n", "es_col = [\n", " \"clase\",\n", " \"forma del sombrero\",\n", " \"superficie del sombrero\",\n", " \"color del sombrero\",\n", " \"magulladuras\",\n", " \"olor\",\n", " \"unión de las láminas\",\n", " \"espaciamiento de las láminas\",\n", " \"tamaño de las láminas\",\n", " \"color de las láminas\",\n", " \"forma del tallo\",\n", " \"raíz del tallo\",\n", " \"superficie del tallo por encima del anillo\",\n", " \"superficie del tallo por debajo del anillo\",\n", " \"color del tallo por encima del anillo\",\n", " \"color del tallo por debajo del anillo\",\n", " \"tipo de velo\",\n", " \"color del velo\",\n", " \"número de anillos\",\n", " \"tipo de anillo\",\n", " \"color de la impresión de esporas\",\n", " \"población\",\n", " \"hábitat\"\n", "]\n", "df.columns = es_col\n", "\n", "X = df.drop('población', axis=1)\n", "y = df['población']\n", "\n", "ct = ColumnTransformer(\n", " transformers=[\n", " ('ohe', OneHotEncoder(handle_unknown='ignore'), X.columns)\n", " ],\n", " remainder='drop'\n", ")\n", "\n", "X_encoded = ct.fit_transform(X)\n", "\n", "x_train, x_test, y_train, y_test = train_test_split(\n", " X_encoded, y, test_size=0.2, random_state=0\n", ")\n", "\n", "print(type(x_train)) # https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html\n", "print(\"-\"*100)\n", "print(x_train.shape) # 113!\n", "print(\"-\"*100)\n", "print(x_train[:3,:3])\n", "\n", "print(\"-+\"*50)\n", "clf = SVC(C=1.0, kernel=\"linear\", random_state=0) \n", "clf.fit(x_train, y_train) \n", "y_pred = clf.predict(x_test)\n", "print(classification_report(y_test, y_pred)) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.10" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }