{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial 2: Clasificación con scikit-learn\n", "\n", "_Miércoles 5 de septiembre de 2018_" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`scikit-learn` (o `sklearn`) es una librería que reúne muchas herramientas para realizar Minería de Datos y _Aprendizaje de Máquinas_. Permite hacer clasificación, clustering, entre otras. Además, incluye varios datasets para aprender a usar la librería.\n", "\n", "En este tutorial vamos a reforzar los conceptos de _aprendizaje supervisado_ y a mostrar cómo usar `sklearn` para entrenar nuestro primer clasificador.\n", "\n", "Puedes ejecutar cada una de las celdas de código haciendo click en ellas y presionando `Shift + Enter`, o bien haciendo click en el ícono ⏭ que aparece al lado izquierdo de la celda. \n", "\n", "También puedes editar cualquiera de estas celdas. Las celdas no son independientes. Es decir, sí importa el orden en el que las ejecutes, y cualquier cambio que hagas se reflejará en las celdas que ejecutes después.\n", "\n", "---\n", "\n", "Cargamos el Iris Dataset que viene en `sklearn`. El dataset incluye atributos de 3 especies de flores." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "from sklearn.datasets import load_iris\n", "\n", "iris = load_iris()\n", "\n", "print(\"Atributos:\", iris.feature_names)\n", "print()\n", "print(\"5 primeras filas:\")\n", "print(iris.data[0:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Los atributos del dataset son el largo y el ancho del pétalo y sépalo de cada flor.\n", "\n", "\"\"\n", "\n", "\n", "Las están dadas por el campo `target`, y los distintos tipos de `target` en el campo `target_names`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"target_names:\", iris.target_names)\n", "print()\n", "print(\"Valores de la columna\")\n", "print(iris.target)\n", "\n", "# vemos que hay 150 observaciones, 50 de cada una de las clases." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que los primeros valores de la columna `target` son ceros en vez del nombre de la especie. Lo que se usa comúnmente es _mapear_ (asignar) números a variables categóricas. En este caso, el 0 corresponde a la primera especie en `target_names`, es decir, a _iris setosa_. El 1 corresponde a _iris versicolor_ y el 2 a _iris virginica_.\n", "\n", "Los datos que vienen en `sklearn` ya están listos para ser usados con los métodos de la librería. Si hubiésemos recibido los datos como una tabla, éstos se verían más o menos así:\n", "\n", "| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | target |\n", "|-------------------|------------------|-------------------|------------------|-----------------|\n", "| 5.1 | 3.5 | 1.4 | 0.2 | iris-setosa |\n", "| 4.9 | 3 | 1.4 | 0.2 | iris-setosa |\n", "| ... | ... | ... | ... | ... |\n", "| 5 | 2 | 3.5 | 1 | iris-versicolor |\n", "| 5.9 | 3 | 5.1 | 1.8 | iris-virginica |\n", "\n", "\n", "Una tarea que se nos podría plantear sería determinar, dados los atributos de una flor, cuál es la especie a la que corresponde. Por ejemplo, ¿a cuál especie corresponde la flor con los siguientes atributos?\n", "\n", "| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | target |\n", "|-------------------|------------------|-------------------|------------------|--------|\n", "| 4.8 | 3 | 1.4 | 0.1 | ??? |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Nuestro primer clasificador\n", "\n", "Vamos a usar un _árbol de decisión_ como nuestro primer clasificador. Un árbol de decisión para este problema puede verse como el de la siguiente imagen:\n", "\n", "![](https://sebastianraschka.com/images/blog/2014/intro_supervised_learning/decision_tree_1.png)\n", "\n", "Nota que podemos mirar cualquiera de los atributos primero, o no usar algún otro atributo, por ejemplo:\n", "\n", "![](https://www.ibm.com/developerworks/library/ba-predictive-analytics2/fig06.gif)\n", "\n", "En el último caso no usamos el `petal width` como atributo para el árbol. Y así, podemos tener muchos árboles distintos.\n", "\n", "El proceso de _entrenar un clasificador_ corresponde al proceso de —en este caso— encontrar las reglas del árbol que _mejor se adapten a nuestros datos_. Llamaremos al árbol resultante el _**modelo**_." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.tree import DecisionTreeClassifier\n", "\n", "# creamos un nuevo clasificador de árbol de decisión\n", "clf = DecisionTreeClassifier()\n", "\n", "# entrenamos el árbol, entregándole los datos de entrenamiento\n", "clf.fit(iris.data, iris.target)\n", "\n", "# el resultado de clf.fit() es el objeto DecisionTreeClassifier con los parámetros que usó para entrenar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos visualizar el árbol generado usando graphviz" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn import tree\n", "import graphviz\n", "\n", "gv = tree.export_graphviz(clf, \n", " out_file=None, \n", " feature_names=iris.feature_names,\n", " class_names=iris.target_names)\n", "graphviz.Source(gv)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ¿Cómo evaluamos nuestro modelo?\n", "\n", "¿Cómo sabemos qué tan bien le fue? Es decir, ¿logró _aprender_ desde los datos cuáles eran las mejores reglas? \n", "\n", "Una forma de ver esto es usando el modelo para clasificar nuevas instancias de los datos.\n", "\n", "Sin embargo, no tenemos _nuevas_ instancias, ya que entrenamos el clasificador con _todos_ los datos disponibles. Si evaluamos nuestro clasificador con los _datos de entrenamiento_ (es decir, los datos que usamos para entrenar el clasificador y generar un modelo), vamos a tener resultados **sobre-optimistas**, ya que el clasificador usó esos mismos datos para entrenar. Es como si fueras a dar una prueba y usaras la misma prueba con las respuestas para estudiar.\n", "\n", "Esto también nos entrega una pista sobre qué significa que un clasificador _aprenda_ de los datos. Para que un modelo se considere _bueno_, no basta con que clasifique correctamente los datos que usó para entrenar, sino que debe clasificar correctamente datos que _no ha visto antes_. Esto es a lo que se llama la capacidad de _generalización_ del modelo.\n", "\n", "Vamos a definir un par de conceptos antes de continuar:\n", "\n", "- El **conjunto de datos de entrenamiento**, o **training set**, es el conjunto de datos que le damos al clasificador para que pueda encontrar las reglas o parámetros óptimos que le permitan predecir la clase de estos datos.\n", "\n", "- El **conjunto de datos de prueba**, o **test set**, es el conjunto de datos sobre el cual vamos a evaluar el rendimiento de nuestro modelo. **Estos datos se eligen antes de cualquier modificación o limpieza del dataset, y sólo se usan para evaluar el modelo entrenado**.\n", "\n", "(Una vez seguros de que nuestro modelo funciona bien y queremos usarlo \"en producción\", podemos entrenar con todos los datos disponibles. No antes)\n", "\n", "El último punto es muy importante. Si por ejemplo, normalizamos los datos primero, y _después_ separamos en training y test sets, estaremos \"contaminando\" nuestros datos de entrenamiento, dándoles información del test set y en cierta forma \"haciendo trampa\", afectando la capacidad de generalización del modelo resultante.\n", "\n", "\n", "### Holdout\n", "\n", "Ahora vamos a tomar una muestra de los datos y separarlos en training set y test set, respectivamente. ¿Cómo determinamos esta muestra?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import train_test_split\n", "\n", "X = iris.data\n", "y = iris.target\n", "\n", "# train_test_split separa X e y en dos conjuntos, train y test\n", "# como hace un muestreo aleatorio, el resultado depende de algún proceso al azar\n", "# el parámetro random_state fija la _semilla aleatoria_ de forma que el resultado siempre será el mismo\n", "# esto es muy útil cuando uno quiere poder reproducir los resultados\n", "# (el uso de 12 como la semilla es totalmente arbitrario)\n", "# a todos ustedes les dará exactamente la misma partición de los datos\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=12)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahora podemos entrenar sólo con el training set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "clf = DecisionTreeClassifier(random_state=12)\n", "clf.fit(X_train, y_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para evaluar, predecimos usando una de las observaciones en el test set y contrastamos el resultado con la respuesta correcta:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x_ = X_test[0]\n", "y_ = y_test[0]\n", "\n", "# la clase es iris-versicolor\n", "print(\"data:\", x_)\n", "print(\"clase:\", y_)\n", "\n", "# [x] es una lista que contiene como elemento a x\n", "# predict recibe una lista de inputs y retorna un arreglo de enteros,\n", "# cada entero es la clase que predice para cada elemento en la lista input\n", "clf.predict([x])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que acertó en este caso. En `sklearn` hay métodos que automatizan este proceso." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import accuracy_score\n", "\n", "y_pred = clf.predict(X_test)\n", "\n", "print(accuracy_score(y_test, y_pred))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que el _accuracy_ es de un 96%. Esto significa que clasificó correctamente el 96% de los datos en `X_test`.\n", "\n", "Surgen dos preguntas a partir de esto:\n", "\n", "1. ¿Tuvimos suerte? Es decir, si hubiésemos elegido otra partición train/test, ¿obtendríamos resultados diferentes?\n", "2. ¿Qué pasa si las clases están desbalanceadas? ¿Cómo afecta al accuracy si tenemos, por ejemplo, 99% de una clase y 1% de otra?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cross-Validation\n", "\n", "_¿Tuvimos suerte? Es decir, si hubiésemos elegido otra partición train/test, ¿obtendríamos resultados diferentes?_\n", "\n", "Cross-validation nos ayuda a disminuir el efecto del azar (pregunta 1). Por ejemplo, observa qué pasa si cambiamos la semilla aleatoria:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=37)\n", "\n", "clf = DecisionTreeClassifier(random_state=12)\n", "clf.fit(X_train, y_train)\n", "y_pred = clf.predict(X_test)\n", "\n", "print(accuracy_score(y_test, y_pred))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para disminuir el efecto, _cross-validation_ particiona los datos en $k$ partes iguales, entrena con $k-1$ partes, evalúa en la $k$-ésima, guarda el resultado, y vuelve a repetir el proceso con otras $k-1$ partes hasta haber recorrido todas las partes. Este proceso se llama $k$-fold cross-validation.\n", "\n", "Observa que esto implica que el clasificador se entrenará $k$ veces, lo cual puede ser costoso dependiendo del clasificador y de la cantidad de datos.\n", "\n", "![](https://upload.wikimedia.org/wikipedia/commons/1/1c/K-fold_cross_validation_EN.jpg)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import KFold\n", "\n", "# 10-fold cv\n", "kf = KFold(n_splits=10)\n", "\n", "accuracies = []\n", "\n", "for train_index, test_index in kf.split(X):\n", " print(\"TRAIN:\", train_index, \"TEST:\", test_index, sep='\\n')\n", " print()\n", " \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", " \n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " clf = DecisionTreeClassifier(random_state=12)\n", " clf.fit(X_train, y_train)\n", " \n", " y_pred = clf.predict(X_test)\n", " \n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print()\n", "print(accuracies)\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que el promedio de los accuracies es $0.94$, más bajo que el $0.96$ y $0.98$ que obtuvimos antes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Un caso extremo de cross-validation es cuando $k = N$, el número de filas en el dataset. Esto significa que vamos a entrenar $N$ veces el clasificador, y cada vez lo vamos a evaluar mirando _un sólo dato a la vez_. Esta forma de cross-validation se llama _leave one out_ (LOO). LOO es útil cuando tenemos pocos datos para entrenar, por lo que dejar, por ejemplo, el 20% de los datos como testing puede ser considerable." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# 150-fold cv\n", "kf = KFold(n_splits=150)\n", "\n", "accuracies = []\n", "\n", "for train_index, test_index in kf.split(X): \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", " \n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " clf = DecisionTreeClassifier(random_state=12)\n", " clf.fit(X_train, y_train)\n", " \n", " y_pred = clf.predict(X_test)\n", " \n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Un problema con `KFold` es que siempre hace las particiones en los mismos lugares. Por ejemplo, observa qué pasa en este caso:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "kf = KFold(n_splits=3)\n", "\n", "accuracies = []\n", "\n", "for train_index, test_index in kf.split(X): \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", " \n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " clf = DecisionTreeClassifier(random_state=12)\n", " clf.fit(X_train, y_train)\n", " \n", " y_pred = clf.predict(X_test)\n", " \n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "¿Por qué el accuracy es 0?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ejemplo con shuffle=True\n", "\n", "kf = KFold(n_splits=3, shuffle=True, random_state=12)\n", "\n", "accuracies = []\n", "\n", "for train_index, test_index in kf.split(X): \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", " \n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " clf = DecisionTreeClassifier(random_state=12)\n", " clf.fit(X_train, y_train)\n", " \n", " y_pred = clf.predict(X_test)\n", " \n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por otra parte, si las clases están desbalanceadas, es necesario hacer un muestreo estratificado (es decir, la distribución de clases de la muestra debe ser fiel a la distribución de clases original). Para eso podemos usar `StratifiedKFold`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.model_selection import StratifiedKFold\n", "\n", "kf = StratifiedKFold(n_splits=3)\n", "\n", "accuracies = []\n", "\n", "# en este caso hay que entregarle tanto X como y a kf.split, ya que hará un split estratificado\n", "for train_index, test_index in kf.split(X, y): \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", " \n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " clf = DecisionTreeClassifier(random_state=12)\n", " clf.fit(X_train, y_train)\n", " \n", " y_pred = clf.predict(X_test)\n", " \n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Medidas de rendimiento de un clasificador\n", "\n", "Volvamos a las preguntas que nos planteamos más arriba:\n", "\n", "1. ¿Tuvimos suerte? Es decir, si hubiésemos elegido otra partición train/test, ¿obtendríamos resultados diferentes?\n", "2. ¿Qué pasa si las clases están desbalanceadas? ¿Cómo afecta al accuracy si tenemos, por ejemplo, 99% de una clase y 1% de otra?\n", "\n", "La (1) la respondimos disminuyendo el factor del azar evaluando varias veces con distintas muestras de los datos usando cross-validation.\n", "\n", "Ahora, ¿qué pasa con el accuracy si tenemos clases desbalanceadas?\n", "\n", "## Clasificador _dummy_\n", "\n", "Un clasificador _dummy_ es un clasficador que no aprende nada de los datos, sino que sus reglas son siempre fijas.\n", "\n", "Por ejemplo, imagina un modelo (para dos clases) que cada vez que viene una nueva observación, lanza una moneda, y dice clase A con un 50% de probabilidad, o clase B con un 50% de probabilidad. Si en los datos de prueba las clases A y B están balanceadas, el clasificador tendrá un 50% de accuracy, _sin haber aprendido nada sobre los datos_.\n", "\n", "De esto se desprende la necesidad de un _baseline_. Es decir, sin hacer nada de esfuerzo, ¿cuál es el mínimo accuracy que puedo obtener? Piensa que si entrenara un clasificador muy sofisticado, y éste al final obtuviese un accuracy menor al baseline, entonces estamos haciendo algo muy mal. El baseline nos permite tener un punto de comparación (el baseline no tiene por qué ser algo muy simple tampoco)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.dummy import DummyClassifier\n", "\n", "kf = StratifiedKFold(n_splits=3)\n", "\n", "accuracies = []\n", "\n", "# vamos a repetir el proceso 100 veces\n", "\n", "for i in range(100):\n", " for train_index, test_index in kf.split(X, y): \n", " X_train = X[train_index]\n", " X_test = X[test_index]\n", "\n", " y_train = y[train_index]\n", " y_test = y[test_index]\n", "\n", " # creamos el dummy classifier con estrategia al azar uniforme\n", " clf = DummyClassifier(strategy=\"uniform\")\n", " clf.fit(X_train, y_train)\n", "\n", " y_pred = clf.predict(X_test)\n", "\n", " accuracy = accuracy_score(y_test, y_pred)\n", " accuracies.append(accuracy)\n", "\n", "print(sum(accuracies) / len(accuracies))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vemos que el accuracy promedio es de aproximadamente 33%, que es la proporción de elementos por cada clase.\n", "\n", "Si las dos clases están desbalanceadas, supongamos en proporción 9:1, **¿qué tipo de clasificador _dummy_ se te ocurre que puede tener un 90% de accuracy?**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Matriz de confusión, Precision y Recall\n", "\n", "La _matriz de confusión_ nos permite observar los errores del clasificador:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import confusion_matrix\n", "\n", "random_seed = 54\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=random_seed)\n", "\n", "clf = DecisionTreeClassifier(random_state=random_seed)\n", "clf.fit(X_train, y_train)\n", "y_pred = clf.predict(X_test)\n", "\n", "confusion_matrix(y_test, y_pred)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "La matriz se interpeta de la siguiente forma:\n", "\n", "| iris-setosa | iris-versicolor | iris-virginica | ← clasificado como / clase real ↓ |\n", "|:----:|:----:|:----:|--------------------:|\n", "| 12 | 0 | 0 | **iris-setosa** |\n", "| 0 | 17 | 1 | **iris-versicolor** |\n", "| 0 | 2 | 18 | **iris-virginica** |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Por cada clase, podemos determinar el tipo de error que el modelo hace.\n", "\n", "- **Verdaderos Positivos (TP)**: el dato X es de la clase C, y el modelo clasifica X como C.\n", "- **Verdaderos Negativos (TN)**: el dato X no es de la clase C, y el modelo clasifica X como algo que no es C.\n", "- **Falsos Positivos (FP)**: el dato X no es de la clase C, pero el modelo clasifica a X como C.\n", "- **Falsos Negativos (FN)**: el dato X es de la clase C, pero el modelo clasifica a X como algo que no es C.\n", "\n", "A partir de estas medidas, definimos dos medidas nuevas para una clase, _precision_ y _recall_:\n", "\n", "$$Precision = \\frac{TP}{TP + FP}$$\n", "\n", "$$Recall = \\frac{TP}{TP+FN}$$\n", "\n", "Nota que estas medidas son para una clase en particular. La medida para todo el dataset puede ser el promedio de la medida para cada clase.\n", "\n", "Ver más en https://en.wikipedia.org/wiki/Precision_and_recall" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import classification_report\n", "\n", "random_seed = 54\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=random_seed)\n", "\n", "clf = DecisionTreeClassifier(random_state=random_seed)\n", "clf.fit(X_train, y_train)\n", "y_pred = clf.predict(X_test)\n", "\n", "print(classification_report(y_test, y_pred))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cuando las clases están desbalanceadas, conviene mucho mirar el Precision y el Recall por cada clase." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Overfitting\n", "\n", "Podemos decir que el _overfitting_ es una _incapacidad de generalización_, usualmente debido a un sobreajuste (overfitting) a los datos con los que contamos.\n", "\n", "Por ejemplo, en nuestro test set obtenemos un 94% de accuracy (y un alto precision y un alto recall). Sin embargo, cuando ponemos nuestro modelo \"en producción\", el accuracy disminuye radicalmente (ej. un 60%). Una posible causa de esto es que nuestro modelo se ajustó demasiado a los datos de entrenamiento.\n", "\n", "El overfitting tiene muchas formas distintas. Mucho de lo que hemos visto en este tutorial apunta a tener buenas garantías de generalización en datos nunca vistos, dado que lo que queremos es poder generalizar a partir de una muestra (muy muy pequeña) a datos totalmente nuevos (de una cantidad arbitraria).\n", "\n", "Algunas formas de evitar el overfitting:\n", "\n", "- Evitar que nuestro modelo sea muy específico a los datos de entrenamiento (por ejemplo, evitando que el árbol de decisión tenga muchas ramas). Es decir, preferir modelos simples a modelos complejos.\n", "- Usar cross-validation para tener una mejor garantía del rendimiento con datos nuevos (aunque esto no es posible si el entrenamiento toma mucho tiempo).\n", "- No \"contaminar\" los datos de entrenamiento con los datos de prueba, o viceversa.\n", "- Normalizar los datos o tratar de disminuir el ruido de éstos. Tener cuidado con los outliers.\n", "- Tener más datos :-)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "# ejemplo obtenido de http://scikit-learn.org/stable/auto_examples/model_selection/plot_underfitting_overfitting.html\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from sklearn.pipeline import Pipeline\n", "from sklearn.preprocessing import PolynomialFeatures\n", "from sklearn.linear_model import LinearRegression\n", "from sklearn.model_selection import cross_val_score\n", "\n", "np.random.seed(0)\n", "\n", "def true_fun(X):\n", " return np.cos(1.5 * np.pi * X)\n", "\n", "n_samples = 30\n", "degrees = [1, 4, 15]\n", "\n", "X = np.sort(np.random.rand(n_samples))\n", "y = true_fun(X) + np.random.randn(n_samples) * 0.1\n", "\n", "\n", "plt.figure(figsize=(14, 5))\n", "for i in range(len(degrees)):\n", " ax = plt.subplot(1, len(degrees), i + 1)\n", " plt.setp(ax, xticks=(), yticks=())\n", "\n", " polynomial_features = PolynomialFeatures(degree=degrees[i],\n", " include_bias=False)\n", " linear_regression = LinearRegression()\n", " pipeline = Pipeline([(\"polynomial_features\", polynomial_features),\n", " (\"linear_regression\", linear_regression)])\n", " pipeline.fit(X[:, np.newaxis], y)\n", "\n", " # Evaluate the models using crossvalidation\n", " scores = cross_val_score(pipeline, X[:, np.newaxis], y,\n", " scoring=\"neg_mean_squared_error\", cv=10)\n", "\n", " X_test = np.linspace(0, 1, 100)\n", " plt.plot(X_test, pipeline.predict(X_test[:, np.newaxis]), label=\"Model\")\n", " plt.plot(X_test, true_fun(X_test), label=\"True function\")\n", " plt.scatter(X, y, edgecolor='b', s=20, label=\"Samples\")\n", " plt.xlabel(\"x\")\n", " plt.ylabel(\"y\")\n", " plt.xlim((0, 1))\n", " plt.ylim((-2, 2))\n", " plt.legend(loc=\"best\")\n", " plt.title(\"Degree {}\\nMSE = {:.2e}(+/- {:.2e})\".format(\n", " degrees[i], -scores.mean(), scores.std()))\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# En conclusión\n", "\n", "El flujo usual a la hora de entrenar un clasificador es el siguiente:\n", "\n", "1. Tener datos. Verificar la fuente de los datos, la existencia de sesgos (sesgo de selección, sesgo del superviviente, sesgos sociodemográficos, etc.).\n", "2. Separar datos en train y test set.\n", "3. Realizar exploración y limpieza de datos en ambos sets, de manera independiente.\n", "4. Elegir clasificadores apropiados para el dominio del problema (próxima clase de cátedra)\n", "5. Determinar métricas de entrenamiento usando cross-validation, si es posible (más de esto en el lab de mañana).\n", "6. Evaluar en el test set.\n", "7. Usar todos los datos para entrenar el modelo que irá \"a producción\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Referencias\n", "\n", "1. Documentación de scikit-learn. http://scikit-learn.org/stable/index.html\n", "2. Precision y Recall. https://en.wikipedia.org/wiki/Precision_and_recall\n", "3. Machine Learning 101 (Google). https://docs.google.com/presentation/d/1kSuQyW5DTnkVaZEjGYCkfOxvzCqGEFzWBy4e9Uedd9k/preview?imm_mid=0f9b7e&cmp=em-data-na-na-newsltr_20171213#slide=id.g168a3288f7_0_58\n", "4. WEKA (un programa visual con clasificadores y otras herramientas para ML). https://www.cs.waikato.ac.nz/ml/weka/\n", "5. Curso de Data Mining con WEKA. https://www.cs.waikato.ac.nz/ml/weka/mooc/dataminingwithweka/" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.5.2" } }, "nbformat": 4, "nbformat_minor": 2 }