{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "BK9dmG7MSOya" }, "source": [ "#
Taller de Aprendizaje Automático
\n", "##
Taller 4: Detección de Anomalías
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Introducción" ] }, { "cell_type": "markdown", "metadata": { "id": "8PjU4ItOTaGr" }, "source": [ "En la siguiente actividad se trabajará en la detección de anomalías sobre redes de computadoras a partir de datos de tráfico. El objetivo será construir un modelo capaz de distinguir entre malas conexiones o ataques, y buenas conexiones, llamadas normales. Para esto se utilizará una parte del conjunto [KDD Cup'99](https://scikit-learn.org/stable/datasets/real_world.html#kddcup99-dataset) pensada para evaluar métodos de detección de anomalías. \n", "\n", "Para los problemas de detección de anomalías generalmente no se cuenta con datos etiquetados para entrenar un detector. Por su definición las anomalías son eventos raros y por lo tanto poco frecuentes, lo que dificulta el etiquetado. Es por esto que este tipo de tareas generalmente son no supervisadas.\n", "\n", "El enfoque más habitual para implementar soluciones para este tipo de problemas, es crear un modelo base a partir de un conjunto de datos \"normales\", es decir de los cuales se tenga cierta certeza de que todos fueron adquiridos en una situación normal. Luego, en producción se detectarán como datos anómalos todos aquellos que no se ajusten a este modelo. Para saber el grado de ajuste de los datos se debe seleccionar un punto de operación, es decir, determinar cuándo un dato se considera anómalo. En un ejemplo real, el cliente primero debería proporcionar una cantidad considerable de datos que representen el comportamiento normal de su sistema. Luego que se tiene el mejor modelo posible de estos datos, junto con el cliente, que es el que conoce su sistema, se debe determinar el punto de operación a partir del compromiso entre detectar la mayor cantidad de anomalías y obtener la menor cantidad de falsas alarmas posibles.\n", "\n", "Para hacer investigación en la detección de anomalías, existen conjuntos de datos como el que se trabajará en esta actividad que si tienen etiquetas. Generalmente estas se obtienen provocando fallas y/o ataques intencionales a un sistema que se encuentra funcionando de manera normal. En esta actividad se separará el conjunto de entrenamiento en dos partes. La primera con una gran proporción de datos etiquetados como normales, simulará ser el conjunto que el cliente nos proporciona para entrenar nuestro modelo. El otro conjunto tendrá datos etiquetados como normales y como anómalos, que se utilizará para definir el punto de operación. Asimismo, se tendrá un conjunto de test asociados a este problema para evaluar la puesta en producción del modelo. " ] }, { "cell_type": "markdown", "metadata": { "id": "hnXnqw5KS3zd" }, "source": [ "## Objetivos\n", "\n", "\n", "* Abordar un problema de detección de anomalías, y ver las diferencias con un problema de clasificación convencional.\n", "* Trabajar con algoritmos de aprendizaje no supervisado.\n", "* Crear detectores compatibles con los *pipelines* de *scikit-learn*.\n" ] }, { "cell_type": "markdown", "metadata": { "id": "common-destiny" }, "source": [ "# Formas de trabajo" ] }, { "cell_type": "markdown", "metadata": { "id": "moral-gallery" }, "source": [ "### Opción 1: Trabajar localmente" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Descargar los datos en su máquina personal y trabajar en su propio ambiente de desarrollo.\n", "\n", "`conda activate TAA-py311` \n", "`jupyter-notebook` " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Los paquetes faltantes se pueden instalar desde el notebook haciendo: \n", "` !pip install paquete_faltante` " ] }, { "cell_type": "markdown", "metadata": { "id": "lined-sport" }, "source": [ "### Opción 2: Trabajar en *Colab*. " ] }, { "cell_type": "markdown", "metadata": { "id": "lined-candle" }, "source": [ "\n", " \n", "
\n", " Ejecutar en Google Colab\n", "
" ] }, { "cell_type": "markdown", "metadata": { "id": "expensive-jewel" }, "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", "metadata": { "id": "iRtWLunGoohY" }, "source": [ "# Datos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 1 - Levantar los Datos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Conjunto de Entrenamiento" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El conjunto de datos [KDD Cup'99](https://scikit-learn.org/stable/datasets/real_world.html#kddcup99-dataset) contiene un conjunto de datos de tráfico de red que incluye tráfico normal y malicioso. El conjunto de datos para entrenamiento contiene 100655 instancias donde cada una cuenta con 41 características entre las que se encuentran la duración de la conexión, los tipos de protocolo, los tipos de servicios, entre otros. Por más información sobre el contenido de las características haga clic [aquí](http://kdd.ics.uci.edu/databases/kddcup99/task.html). Además, se cuenta con la columna *'labels'* que indica si el dato es normal o, de no serlo, indica el tipo de anomalía.\n", "\n", "#### Ejercicios:\n", "\n", " - Ejecute la siguientes celdas para levantar el conjunto de datos de entrenamiento. \n", " - Observar la cantidad de muestras por tipo. \n", " - Determinar la relación entre datos normales y anómalos. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import numpy as np\n", "import time\n", "from sklearn.datasets import fetch_kddcup99\n", "\n", "#Se obtienen los datos en formato de diccionario\n", "KDDSA = fetch_kddcup99(subset='SA', as_frame=True, random_state=42)\n", "\n", "#A partir del diccionario se crea un Dataframe con los datos\n", "df = pd.DataFrame(data=KDDSA.frame.values, columns=KDDSA.frame.columns)\n", "\n", "#Se estandariza el formato de los datos en el Dataframe \n", "types = [float, str, str,str, float, float, str, float, float, float, float, str, float, float,float, float, float, float, float, float, str, str, \n", " float, float, float, float,float, float, float, float,float, float, float, float, float, float, float,float, float, float, float, str]\n", "\n", "columns = df.columns\n", "for i in range(len(columns)):\n", " df[columns[i]] = df[columns[i]].astype(types[i])\n", " if types[i] == str:\n", " df[columns[i]]= df[columns[i]].str.replace(\"b'\", \"\")\n", " df[columns[i]]= df[columns[i]].str.replace(\"'\", \"\")\n", "\n", "#Visualizo\n", "df.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Conjunto de Test" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Este problema tiene disponible un conjunto de datos para Test, estos se pueden encontar [aquí](https://kdd.ics.uci.edu/databases/kddcup99/kddcup99.html). \n", "\n", "#### Ejercicios:\n", "\n", " - Descargar y levantar los datos de Test. \n", " - Separar los datos de las etiquetas." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pip install wget" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#start\n", "start = time.time()\n", "\n", "#Importo wget \n", "import wget\n", "\n", "#Descargo los datos de Test\n", "wget.download('http://kdd.ics.uci.edu/databases/kddcup99/corrected.gz')\n", "\n", "#end\n", "end = time.time()\n", "print(np.round(end - start,3), 'segundos')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Levanto los datos de Test\n", "test_data = pd.read_csv('./corrected.gz', header=None)\n", "\n", "test_data.columns = columns\n", "\n", "#Se estandariza el formato de los datos en el Dataframe \n", "types = [float, str, str,str, float, float, str, float, float, float, float, str, float, float,float, float, float, float, float, float, str, str, \n", " float, float, float, float,float, float, float, float,float, float, float, float, float, float, float,float, float, float, float, str]\n", "\n", "for i in range(len(columns)):\n", " test_data[columns[i]] = test_data[columns[i]].astype(types[i])\n", "\n", "test_data.head()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2022-04-01T13:44:45.376213Z", "start_time": "2022-04-01T13:44:45.374544Z" } }, "source": [ "### Parte 2 - Preparar Conjuntos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ejercicios:\n", "\n", "Manteniendo el orden de los datos de Entrenamiento:\n", "\n", " - Separar las primeras 90000 muestras para entrenamiento con sus respectivas etiquetas. Estos deberían estar etiquetados como *normales*.\n", " - Separar las restantes muestras y sus respectivas etiquetas para validación. Este conjunto permitirá fijar el punto de operación del modelo." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2022-04-01T13:44:51.617335Z", "start_time": "2022-04-01T13:44:51.378501Z" } }, "outputs": [], "source": [ "#Verifico las anomalias presentes en cada conjunto\n", "print('Tipo de datos en Entrenamiento:\\n',np.unique(y_train))\n", "print('Tipo de datos en Validacion:\\n',np.unique(y_val))\n", "print('Tipo de datos en Test:\\n',np.unique(y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El código anterior muestra la cantidad de clases presente en cada conjunto. Verifique que en el conjunto de Entrenamiento solo hay muestras normales. \n", "\n", "Es importante tener en cuenta que los datos de Test presentan una distribución de probabilidad diferente a la de los datos de entrenamiento y validación, de hecho, contienen tipos específicos de ataques. Este aspecto es crucial para hacer la tarea de detección de anomalías más realista.\n", "\n", "Los ataques informáticos pueden ser clasificados en diferentes categorías, las cuales se definen en función de su objetivo y método de ejecución. Es usual, categorizar los ataques en cuatro tipos principales: \n", "\n", "* **DoS**: Tienen como objetivo abrumar un sistema o red con una gran cantidad de tráfico, lo que puede resultar en una disminución o pérdida total del servicio , e.g. syn flood;\n", "* **R2L**: Buscan obtener acceso no autorizado a un sistema desde una máquina remota, e.g. adivinar la contraseña;\n", "* **U2R**: Buscan obtener acceso no autorizado a privilegios de superusuario local o raíz., e.g., various ''buffer overflow'' attacks;\n", "* **probing**: Consisten en la vigilancia y exploración de, por ejemplo, puertos y/o servicios en busca de vulnerabilidades que puedan ser explotadas posteriormente.\n", "\n", "A continuación, se otorga un diccionario de correspondencia entre las anomalías y la categoría correspondiente." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Se define un diccionario con la correspondencia entre la anomalía y la categoría\n", "attack_dict = {\n", " 'normal.': 'normal',\n", " \n", " 'back.': 'DoS',\n", " 'land.': 'DoS',\n", " 'neptune.': 'DoS',\n", " 'pod.': 'DoS',\n", " 'smurf.': 'DoS',\n", " 'teardrop.': 'DoS',\n", " 'mailbomb.': 'DoS',\n", " 'apache2.': 'DoS',\n", " 'processtable.': 'DoS',\n", " 'udpstorm.': 'DoS',\n", " \n", " 'ipsweep.': 'Probe',\n", " 'nmap.': 'Probe',\n", " 'portsweep.': 'Probe',\n", " 'satan.': 'Probe',\n", " 'mscan.': 'Probe',\n", " 'saint.': 'Probe',\n", "\n", " 'ftp_write.': 'R2L',\n", " 'guess_passwd.': 'R2L',\n", " 'imap.': 'R2L',\n", " 'multihop.': 'R2L',\n", " 'phf.': 'R2L',\n", " 'spy.': 'R2L',\n", " 'warezclient.': 'R2L',\n", " 'warezmaster.': 'R2L',\n", " 'sendmail.': 'R2L',\n", " 'named.': 'R2L',\n", " 'snmpgetattack.': 'R2L',\n", " 'snmpguess.': 'R2L',\n", " 'xlock.': 'R2L',\n", " 'xsnoop.': 'R2L',\n", " 'worm.': 'R2L',\n", " \n", " 'buffer_overflow.': 'U2R',\n", " 'loadmodule.': 'U2R',\n", " 'perl.': 'U2R',\n", " 'rootkit.': 'U2R',\n", " 'httptunnel.': 'U2R',\n", " 'ps.': 'U2R', \n", " 'sqlattack.': 'U2R',\n", " 'xterm.': 'U2R'\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Antes de finalizar se deberán cambiar las etiquetas de los datos en todos los conjuntos a 0 y 1. Siendo 0 la etiqueta de la clase normal y 1 la etiqueta de la clase anómala.\n", "\n", "#### Objetivos:\n", " - Cambiar las etiquetas de los datos en todos los conjuntos a 0 y 1.\n", " \n", "**Comentario**: Se sugiere en alguna variable distinta conservar los valores originales." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 3 - Preprocesar y Visualizar los Datos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Una vez armados los conjuntos de datos, se espera que se realice una exploración y análisis de los mismos. Conforme a lo anterior, se espera que se implemente un pipeline de preprocesamiento adecuado para los datos en cuestión. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ejercicios:\n", "\n", " - Analizar y visualizar los datos. \n", " - Implementar un **pipeline** que realice los los preprocesamientos que considere necesarios. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": { "id": "AAXVfNNz0DZe" }, "source": [ "# Detección de anomalías\n", "\n", "Si bien *scikit-learn* proporciona algunas herramientas para la detección de anomalías, esta actividad se centra en la utilización de algoritmos no supervisados generalmente pensados para otras tareas como: reducción de la dimensionalidad, y/o *clustering*. Específicamente se trabajará con: PCA, *K-Means*, y *Gaussian Mixture Model* (GMM)." ] }, { "cell_type": "markdown", "metadata": { "id": "rBVPxdE27wvI" }, "source": [ "## PCA\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "El análisis de componentes principales (**PCA**) es una técnica de reducción de la dimensionalidad que permite representar los datos en un espacio de menor dimensión manteniendo la mayor cantidad posible de información. \n", "\n", "En la *Parte 1* se deberá seleccionar la cantidad de componentes principales que se utilizarán en el modelo. También, se sugiere utilizar esta técnica como una herramienta de visualización de datos.\n", "\n", "Luego, en la *Parte 2* se implementa un detector de anomalías utilizando esta técnica." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 1\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ejercicios:\n", "\n", " - Aplicar PCA sobre los datos de entrenamiento y graficar como varía el porcentaje de la varianza total en función de la cantidad de componentes principales (CPs). Se sugiere ver la sección *Choosing the Right Number of Dimensions* del capítulo 8 del libro. \n", " - Determinar la cantidad de CPs de manera de mantener el *99%* de la varianza de los datos.\n", " - Utilizando PCA visualice los datos en dos o tres dimensiones. ¿Logra identificar clusters? *Para esta parte puede resultar útil comparar según la proyeccion de datos Normales contra las proyecciones de los datos Anómalos según el tipo de ataque.*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La forma más directa de hacer detección de anomalías utilizando PCA, es mediante el error de reconstrucción. Para esto primero se calculan los componentes principales a partir de los datos de entrenamiento. Luego para cada dato a analizar se lo proyecta sobre estos componentes, y se calcula su reconstrucción. Debido a que los CPs fueron calculado sólo con datos normales, se espera que la reconstrucción de datos anómalos tengan mayores errores. Es por esto que a partir del error de reconstrucción se puede determinar si un dato es anómalo o no.\n", "\n", "#### Objetivos:\n", "\n", "* Implementar un detector tal como se describe arriba, utilizando RMSE para calcular el error. El mismo se debe definir como una clase de manera que sea compatible con los *pipelines* de *scikit-learn*. En la siguiente celda se muestra un *template* para crear la clase ([aquí](https://scikit-learn.org/stable/developers/develop.html) se pueden ver otros ejemplos).\n", "\n", "* Crear un *pipeline* que incluya el preprocesamiento y el detector implementado.\n", "* Entrenar el modelo de manera que mantenga el *99%* de la varianza. \n", "* Proponga un punto de operación teniendo en cuenta que se quiere evitar un exceso de falsas alarmas. Para ello se recomienda graficar el compromiso entre *precision* y *recall* para distintos valores de *threshold* que definen el punto de operación. Ver la sección *Precision/Recall Trade-off* del capítulo 3 del libro.\n", "* Graficar los *scores* de los datos utilizados en el punto anterior, diferenciando con colores los datos normales de los anómalos.\n", "* Evaluar el desempeño en el conjunto de Validación y Test. Puede resultar útil tener la tasa de aciertos por categoría de anomalía." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2022-04-01T15:30:16.680311Z", "start_time": "2022-04-01T15:30:16.676436Z" } }, "outputs": [], "source": [ "# Importo las librerías necesarias\n", "from sklearn.base import BaseEstimator, OutlierMixin\n", "from sklearn.utils.validation import check_array, check_is_fitted\n", "from sklearn.decomposition import PCA\n", "from sklearn.metrics import mean_squared_error\n", "\n", "# Defino la clase AD_PCA que hereda de BaseEstimator y OutlierMixin lo que permite que sea compatible con el pipeline. \n", "# Esta clase debe tener los métodos fit y score. \n", "class AD_PCA(BaseEstimator, OutlierMixin):\n", " def __init__(self, n_comp=None):\n", " '''\n", " \n", " Constructor de la clase.\n", "\n", " Parametros:\n", " n_comp: cantidad de componentes principales a utilizar\n", " \n", " '''\n", " \n", " self.n_comp = n_comp\n", " \n", " def fit(self, X, y=None):\n", " '''\n", " \n", " Se entrena el modelo.\n", "\n", " Parametros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", " \n", " Retorna:\n", " self: el objeto\n", " \n", " '''\n", "\n", " self.X = X\n", " self.y = y \n", " \n", " # Agregar código---\n", "\n", " \n", " #------------------\n", " \n", " return self\n", " \n", " def score(self, X, y=None): \n", " '''\n", " \n", " Se calcula el error de reconstrucción de cada muestra. \n", " \n", "\n", " Parametros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", "\n", " Retorna:\n", " score: el RMSE de cada muestra reconstruida. \n", "\n", " '''\n", " \n", " # Se verifica que los datos sean válidos\n", " X = check_array(X)\n", " \n", " # Se verifica que el modelo haya sido entrenado\n", " check_is_fitted(self, ['X', 'y'])\n", "\n", " # Agregar código---\n", " \n", "\n", " #------------------\n", " return score" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se otorga una función que, dadas las predicciones, devuelve la tasa de acierto por tipo de anomalía." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def aciertos_por_clase(y, y_pred ,y_g_truth):\n", " \n", " '''\n", " \n", " Parametros:\n", " y = Etiquetas del conjunto en Formato 0 o 1\n", " y_pred = Predicciones en el conjunto en Formato True o False\n", " y_g_truth = Etiquetas del conjunto en formato string indicando el tipo de anomalía. \n", " \n", " Retorna:\n", " Devuelve un Dataframe con la tasa de aciertos por Clase.\n", " \n", " '''\n", " \n", " aciertos = ( (y_pred.flatten()*1) == y.flatten())\n", " type_of_anomaly = list(np.unique(y_g_truth))\n", " acc_class = []\n", " \n", " for i_an in range(len(type_of_anomaly)):\n", " mask_anomaly = y_g_truth == type_of_anomaly[i_an] \n", " total_tipo = np.count_nonzero(mask_anomaly)\n", " acc_class.append(np.count_nonzero(aciertos & mask_anomaly)/total_tipo)\n", " \n", " return pd.DataFrame([acc_class], columns=type_of_anomaly)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": { "id": "7zI3BtzycQIB" }, "source": [ "## K-Means" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A continuación se implementará un detector de anomalías utilizando el modelo K-Means.\n", "\n", "En la *Parte 1*, se llevará a cabo la determinación del número óptimo de clusters necesarios para el problema. En la *Parte 2* se implementará un detector de anomalías.\n", "\n", "Para facilitar la construcción del detector, se sugiere implementar un único pipeline que incluya tanto el preprocesamiento como el modelo. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 1\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Ejercicio:\n", "\n", " - Implementar una forma de hallar la cantidad de clusters de K-Means óptima . Se sugiere ver la sección *Finding the optimal number of clusters* del Capítulo 9. \n", " \n", "**Comentario**: Emplear alguno de los métodos sugeridos toma un tiempo no menor, se sugiere: (i) economizar las búsquedas, (ii) utilizar semillas, (iii) en cuánto tenga los resultados guardelos en formato *.npy*, *.txt* u otro que considere, para que luego pueda cargar en caso que desee rehacer una gráfica. Seguir esta práctica evita que tenga que volver a correr el experimento. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 2\n" ] }, { "cell_type": "markdown", "metadata": { "id": "XnYteDcOxmr0" }, "source": [ "Una vez seleccionada la cantidad óptima de clusters, se procederá a construir el detector de anomalías utilizando el modelo K-Means. Hay diferentes formas de usar este algoritmo, se sugiere entrenar el modelo primero y luego calcular la distancia al cluster más cercano. Si esta distancia supera un umbral a definir entonces la muestra se clasificará como anómala.\n", "\n", "#### Ejercicios:\n", "\n", "- Implementar un detector de anomalías utilizando *K-Means*. Para ello cree una clase y un *pipeline* que lo implemente de forma análoga a lo realizado con el método de PCA.\n", "- Proponga un punto de operación teniendo en cuenta el compromiso entre *precision* y *recall* para distintos valores de *threshold* que definen el punto de operación. \n", "- Graficar los *scores* de los datos utilizados en el punto anterior, diferenciando con colores los datos normales de los anómalos. \n", "- Evaluar el desempeño en el conjunto de Validación y Test." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2022-03-30T08:45:10.726332Z", "start_time": "2022-03-30T08:45:10.719951Z" }, "id": "ZH-w5Vnq0xYR" }, "outputs": [], "source": [ "# Importo las bibliotecas necesarias\n", "from sklearn.base import BaseEstimator, OutlierMixin\n", "from sklearn.cluster import KMeans\n", "from sklearn.utils.validation import check_array, check_is_fitted\n", "\n", "\n", "# Defino la clase AD_Kmeans que hereda de BaseEstimator y OutlierMixin lo que permite que sea compatible con el pipeline.\n", "# Esta clase debe tener los métodos fit y score.\n", "class AD_Kmeans(BaseEstimator, OutlierMixin):\n", " def __init__(self, n_clusters=None):\n", " '''\n", " \n", " Constructor de la clase.\n", "\n", " Parametros:\n", " n_clusters: cantidad de clusters a utilizar\n", " \n", " '''\n", "\n", " self.K = n_clusters\n", " \n", " def fit(self, X, y=None):\n", " '''\n", " Se entrena el modelo.\n", "\n", " Parametros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", "\n", " Retorna:\n", " self: el objeto\n", " '''\n", "\n", " self.X = X\n", " self.y = y \n", " \n", " # Agregar código---\n", "\n", " \n", " #------------------\n", " \n", " return self\n", " \n", " def score(self, X, y=None):\n", " '''\n", " \n", " Se calcula la distancia mínima de cada muestra a los centroides de los clusters.\n", "\n", " Parámetros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", " \n", " Retorna:\n", " d_min: la distancia mínima de cada muestra a los centroides de los clusters.\n", " \n", " '''\n", "\n", " # Se verifica que los datos sean válidos\n", " X = check_array(X)\n", "\n", " # Se verifica que el modelo haya sido entrenado\n", " check_is_fitted(self, ['X', 'y'])\n", "\n", " # Agregar código---\n", "\n", "\n", " #------------------\n", " \n", " return d_min" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": { "id": "U2d6LJtglvN2" }, "source": [ "## Gaussian Mixtures Models" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por último, se implementará un detector de anomalías utilizando el modelo mezcla de gaussianas (GMM).\n", "\n", "En la *Parte 1*, se llevará a cabo la determinación del número óptimo de clusters necesarios para el problema. Una vez seleccionado el número adecuado de clusters, se procederá a la construcción del clasificador en la *Parte 2*.\n", "\n", "Nuevamente, para facilitar la construcción del detector, se sugiere implementar un único pipeline que incluya tanto el preprocesamiento como el modelo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parte 1\n" ] }, { "cell_type": "markdown", "metadata": { "id": "RFpfYG1Q7-mJ" }, "source": [ "Siga el ejemplo de la sección *Anomaly Detection Using Gaussian Mixture* en el Capítulo 9 del libro, y determine la cantidad óptima de mezclas a utilizar por el modelo.\n", "\n", "\n", "#### Ejercicio:\n", "\n", " - Proponer una forma de hallar la cantidad óptima de mezclas a utilizar.\n", " \n", " **Comentario**: Emplear alguno de los métodos sugeridos toma un tiempo no menor, se sugiere: (i) economizar las búsquedas, (ii) utilizar semillas, (iii) en cuánto tenga los resultados guardelos en formato *.npy*, *.txt* u otro que considere, para que luego pueda cargar en caso que desee rehacer una gráfica. Seguir esta práctica evita que tenga que volver a correr el experimento. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Parte 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Siguiendo también el el ejemplo de la sección *Anomaly Detection Using Gaussian Mixture* en el Capítulo 9 del libro, construya y entrene un clasificador de anomalías utilizando la cantidad de mezclas determinadas en la parte anterior. \n", "\n", "Una vez entrenado el modelo, se calcula la log-verosimilitud de cada muestra y se umbraliza para detectar anomalías. Si el valor de la log-verosimilitud es menor que un umbral determinado, se considera que la muestra es anómala.\n", "\n", "El valor del umbral se fija a partir de determinar la proporción de muestras que se clasifican como anómalas. Por ejemplo, si se sabe de antemano que los ataques constituyen un porcentaje $\\alpha$ de las muestras, se puede calcular el valor que define el percentil correspondiente a este $\\alpha$% en la escala de log-verosimilitud para clasificar las muestras como anómalas." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "#### Ejercicios:\n", "\n", "* Implementar un detector que calcule el valor de los *scores*, la log-verosimilitud en este caso. En la siguiente celda se proporciona un *template* para la implementación del detector.\n", "* Fijar un umbral en validación utilizando algún percentil similar a lo realizado en el Capítulo 9 del libro. Se obtendrán distintos puntos de funcionamiento en función del percentil elegido. Discutir sobre los resultados obtenidos.\n", "* Evaluar el desempeño en el conjunto de Validación y Test. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "ExecuteTime": { "end_time": "2022-03-30T08:45:15.141046Z", "start_time": "2022-03-30T07:48:01.201Z" } }, "outputs": [], "source": [ "# Importo las bibliotecas necesarias\n", "from sklearn.mixture import GaussianMixture\n", "from sklearn.base import BaseEstimator, OutlierMixin\n", "from sklearn.utils.validation import check_array, check_is_fitted\n", "\n", "# Defino la clase AD_GMM que hereda de BaseEstimator y OutlierMixin lo que permite que sea compatible con el pipeline.\n", "# Esta clase debe tener los métodos fit y score.\n", "class AD_GMM(BaseEstimator, OutlierMixin):\n", " def __init__(self, n_comp=None):\n", " '''\n", " \n", " Constructor de la clase.\n", "\n", " Parametros:\n", " n_comp: cantidad de componentes principales a utilizar\n", " \n", " '''\n", "\n", " self.n_comp = n_comp\n", " \n", " def fit(self, X, y=None):\n", " '''\n", " \n", " Se entrena el modelo.\n", "\n", " Parametros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", "\n", " Retorna:\n", " self: el objeto\n", "\n", " '''\n", "\n", " self.classes_ = [1, 0]\n", " self.X = X\n", " self.y = y \n", " \n", " # Agregar código---\n", " \n", " \n", " \n", " #------------\n", " return self\n", " \n", " def score(self, X, y=None):\n", " '''\n", " \n", " Se calcula la log-verosimilitud de cada muestra.\n", " \n", " Parametros:\n", " X: matriz de datos\n", " y: etiquetas (no son necesarias)\n", " \n", " Retorna:\n", " score: el score de cada muestra.\n", " \n", " '''\n", "\n", " # Se verifica que los datos sean válidos\n", " X = check_array(X)\n", "\n", " # Se verifica que el modelo haya sido entrenado\n", " check_is_fitted(self, ['X', 'y'])\n", "\n", " # Agregar código---\n", "\n", "\n", " \n", " #------------------\n", " \n", " return score" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": true, "id": "PoNvBYnaqyqT" }, "source": [ "# Opcional\n", "\n", "* Aplicar a los datos del problema alguno de los detectores de *sikit-learn* como: One-Class SVM, Isolation Forest\n", "* Realizar ingeniería de características.\n", "* Combinar PCA con K-Means y/o GMM\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# .." ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "Taller4_detección_de_anomalías.ipynb", "provenance": [], "toc_visible": true }, "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 }, "vscode": { "interpreter": { "hash": "cc2c5d5340d8ad55e03c25f3a5af2b63f782b76cc40ef62190c4c92ecdbc3804" } } }, "nbformat": 4, "nbformat_minor": 4 }