Está en la página 1de 248

4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Introducción al aprendizaje computacional y


scikit-learn

En este taller guiado haremos un acercamiento básico a varios de los pasos más
importantes del aprendizaje computacional usando la librería especializada Scikit-learn.
Veremos cómo:
cargar conjuntos de datos.
preprocesar datos numéricos y categóricos.
crear particiones de datos entrenamiento - prueba.
entrenar modelos (regresión logística) para clasificación.
evaluar el desempeño de modelos para clasificación.

1. Importar scikit-learn
Scikit-learn es una librería especializada de aprendizaje computacional para el lenguaje de
programación Python. Cuenta con múltiples paquetes y submódulos que serán importados
en sus respectivas secciones.
El paquete de Python de Scikit-learn de pip se debe instalar/actualizar con el nombre
scikit-learn :

In [1]: # Usamos el gestor de paquetes pip para instalar 'scikit-learn'


!pip install -U scikit-learn

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 1/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-
packages (1.2.2)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.10/dist
-packages (from scikit-learn) (1.22.4)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
10/dist-packages (from scikit-learn) (3.1.0)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist
-packages (from scikit-learn) (1.2.0)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.10/dist-
packages (from scikit-learn) (1.10.1)

Nota: El nombre del módulo de Python NO es scikit-learn . Para


importarlo se debe usar el nombre sklearn .
In [2]: # No se debe confundir el nombre al importar la librería.
import sklearn

La manera en que importe los paquetes y funciones es una elección personal. Por ejemplo,
puede decidir importar todas las definiciones de un paquete:
In [3]: # Importa todas las definiciones dentro del submódulo "sklearn.datasets"
from sklearn.datasets import *

iris = load_iris()

Importar solo las necesarias:


In [4]: # Importa la definición "load_iris" del submódulo "sklearn.datasets"
from sklearn.datasets import load_iris

iris = load_iris

O importar un paquete y usar sus definiciones:


In [5]: # Importa el submódulo "sklearn.datasets"
from sklearn import datasets

iris = datasets.load_iris()

Además de Scikit-learn, utilizaremos algunas librerías de utilidad básicas del ecosistema de


computación científica de Python.
In [6]: # Librerías NumPy, Pandas y Matplotlib para el análisis y manipulación de datos

import numpy as np
import pandas as pd
import matplotlib as mpl

In [7]: # Versiones de Python, NumPy y Pandas.

!python --version

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 2/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
print('NumPy', np.__version__)
print('Pandas', pd.__version__)
print('Matplotlib', mpl.__version__)
print('Scikit-learn', sklearn.__version__)

Python 3.10.11
NumPy 1.22.4
Pandas 1.5.3
Matplotlib 3.7.1
Scikit-learn 1.2.2

Este material se realizó con las siguientes versiones:


Python: 3.7.10
NumPy: 1.19.5
Pandas: 1.1.5
Matplotlib: 3.2.2
Scikit-learn: 0.24.1

2. Cargar datos
Los modelos de Scikit-learn aceptan datos numéricos almacenados en arreglos de Numpy o
en matrices dispersas de Scipy. Otros tipos de datos convertibles a arreglos de Numpy
también son aceptados, como listas y DataFrames de Pandas.
Scikit-learn ofrece funciones de utilidad para cargar una pequeña colección de populares
conjuntos de datos, además de ofrecer funciones de utilidad para generar conjuntos de
datos sintéticos.
2.1. Conjuntos de datos
Scikit-learn ofrece funciones de utilidad en el paquete datasets para cargar conjuntos de
datos populares, los cuales suelen ser usados para evaluar el desempeño de algoritmos de
aprendizaje computacional. Estos conjuntos de datos suelen ser llamados "conjuntos de
juguete" o benchmark.
2.1.1. Loaders
En el caso de conjuntos de datos pequeños, scikit-learn cuenta con una familia de
funciones llamada loaders. Estos conjuntos de datos vienen incluidos con la instalación de
scikit-learn y no implican un uso de red adicional para descargarlos.
Por ejemplo, veamos uno de los conjuntos de datos más populares: el conjunto de datos de
la familia de flores Iris. Usaremos la función sklearn.datasets.load_iris :

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 3/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [8]: # sklearn.datasets es el submódulo destinado a la carga de conjuntos de datos.


from sklearn import datasets

iris = datasets.load_iris()

print(type(iris))

<class 'sklearn.utils._bunch.Bunch'>

Como podemos ver, el conjunto de datos cargado pertenece a la clase


sklearn.utils.Bunch . Los objetos Bunch son diccionarios que exponen sus llaves,
como los subconjuntos del dataset y demás información descriptiva, en forma de atributos.
Veamos las llaves de iris :
In [ ]: print(iris.keys())

dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_name


s', 'filename', 'data_module'])

En este caso nos interesan las llaves data , target , target_names y


feature_names .

Las llaves pueden ser accedidas a través de llaves cuadradas ( [] ) o como un atributo a
través de la notación con punto ( . ).
In [ ]: # Las dos líneas de código retornan lo mismo.
print(iris.feature_names)
print(iris['feature_names'])

['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (c
m)']
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (c
m)']

Iris es un conjunto de datos para la clasificación de flores de la familia Iris en 3 especies


distintas (setosa, versicolor y virginica).
In [ ]: # target_names: Nombre de las etiquetas de la variable objetivo.
print(iris.target_names)

['setosa' 'versicolor' 'virginica']

Basado en la longitud y anchura de sus pétalos y sépalos en centímetros.


In [ ]: # feature_names: Nombre de las variables o características.
print(iris.feature_names)

['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (c
m)']

En general, las feature o características hacen referencia a las variables que usaremos para
entrenar nuestros modelos de aprendizaje computacional y predecir el target u objetivo,
también llamado etiqueta.

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 4/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

En aprendizaje computacional a un punto, instancia o muestra de un conjunto de datos se le


da el nombre de ejemplo. Cuando un ejemplo tiene asociado una etiqueta se habla de
datos etiquetados y cuando no, de datos no etiquetados.
En aprendizaje supervisado usamos datos etiquetados y algoritmos para entrenar
modelos los cuales serán usados para predecir la etiqueta de ejemplos de los cuáles no se
conoce la etiqueta.
Acabamos de ver los nombres de las características y los nombres de las posibles etiquetas
del conjunto de datos Iris. Para acceder a los datos correspondientes a estos nombres y
etiquetas debemos acceder a ellos a través de las llaves data y target .
La llave data es una matriz que contiene por cada ejemplo una fila con las 4
características mencionadas.
In [ ]: print(iris.data.shape)

(150, 4)

La llave target es un vector que contiene la etiqueta para cada ejemplo.


In [ ]: print(iris.target.shape)

(150,)

Como acabamos de ver, el conjunto de datos Iris cuenta con ejemplos y 150 4

características.
Por ejemplo, si quisiéramos saber las características de unos ejemplos y su etiqueta
podemos hacer lo siguiente:
In [ ]: # Lista de ids de los ejemplos que vamos a consultar
ids = [0, 25, 50, 100]

for i in ids:
print(f'Ejemplo: {i}')
print(f'Características: {iris.data[i]}, etiqueta: {iris.target[i]}\n')

print('Nombres de las características:', iris.feature_names)


print('Nombres de las etiquetas:', iris.target_names)

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 5/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
Ejemplo: 0
Características: [5.1 3.5 1.4 0.2], etiqueta: 0

Ejemplo: 25
Características: [5. 3. 1.6 0.2], etiqueta: 0

Ejemplo: 50
Características: [7. 3.2 4.7 1.4], etiqueta: 1

Ejemplo: 100
Características: [6.3 3.3 6. 2.5], etiqueta: 2

Nombres de las características: ['sepal length (cm)', 'sepal width (cm)', 'pet
al length (cm)', 'petal width (cm)']
Nombres de las etiquetas: ['setosa' 'versicolor' 'virginica']

Las características están en el mismo orden que feature_names . Es decir, el ejemplo


100 tiene:
6.3 cm de longitud de sépalo ( sepal length (cm) ).
3.3 cm de ancho de sépalo ( sepal width (cm) ).
6.0 cm de longitud de pétalo ( petal length (cm) ).
2.5 cm de ancho de pétalo ( petal width (cm) ).
También podemos observar que las etiquetas están representadas con números. De manera
similar, están en el mismo orden que target_names . Es decir:
El ejemplo tiene etiqueta setosa .
0

El ejemplo tiene etiqueta veriscolor .


50

El ejemplo tiene etiqueta virginica .


100

En Scikit-learn es estándar manejar los datos en el formato X, y , donde X es una matriz


donde las filas representan ejemplos y las columnas características y y es un vector que
representa la etiqueta u variable objetivo. Podemos notar que X corresponde a la llave
data y y a target en nuestro ejemplo anterior.

Usualmente, se tratará de convertir los datos que se tengan a este formato, con el objetivo
de tenerlos listos para ser preprocesados y procesados por un algoritmo de aprendizaje
computacional.
Por ejemplo, para obtener Iris en el formato X, y podríamos hacer lo siguiente:
In [ ]: # Desempaquetado de tuplas a partir de los atributos.
X, y = iris.data, iris.target

Sin embargo, también es posible (y recomendado) cargar directamente el conjunto de datos


en este formato con el argumento booleano return_X_y :
In [ ]: # El argumento 'return_X_y' permite obtener directamente ambos arreglos.
X, y = datasets.load_iris(return_X_y=True)

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 6/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

2.1.2. Fetchers
En el caso de conjuntos de datos más grandes, scikit-learn ofrece la familia de funciones
fetchers. Estos conjuntos de datos no están incluidos en la instalación de scikit-learn y
deben ser descargados con recursos de red.
Al ejecutar la siguiente celda notará que se hará un llamado para descargar el conjunto de
datos california housing (regresión).
In [ ]: # Los Fetcher también hacen parte del submódulo sklearn.datasets.
from sklearn import datasets

# Este tipo de funciones empieza con el prefijo '.fetch_'


california_housing = datasets.fetch_california_housing()

Al igual que muchos otros fetchers y loaders, el dataset california_housing es un objeto


Bunch y puede ser cargado directamente en el formato X, y directamente.

In [ ]: print(type(california_housing))

<class 'sklearn.utils._bunch.Bunch'>

In [ ]: # Lo cargamos en formato X, y con el argumento 'return_X_y'.


X, y = datasets.fetch_california_housing(return_X_y=True)

2.2. Conjuntos de datos sintéticos


Scikit-learn permite generar una serie de conjuntos de datos sintéticos, los cuales pueden
ser útiles para probar el desempeño de un algoritmo de aprendizaje computacional. Estos
métodos, a diferencia de los loaders y fetchers no retornan un objeto Bunch sino un
conjunto de datos en el formato X, y .
Veamos algunos ejemplos. Graficaremos los resultados con matplotlib:
In [ ]: # Importamos y configuramos la librería de visualización Matplotlib.
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.dpi'] = 110

In [ ]: # Conjunto de datos Blobs


X, y = datasets.make_blobs()

plt.scatter(X[:,0], X[:,1], c=y);

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 7/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [ ]: # Conjunto de datos Moons


X, y = datasets.make_moons(n_samples=100)

plt.scatter(X[:,0], X[:,1], c=y);

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 8/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [ ]: # Conjunto de datos para clasificación.


X, y = datasets.make_classification()

plt.scatter(X[:,0], X[:,1], c=y);

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 9/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Los conjuntos de datos generados de esta manera son generados de manera aleatoria, es
decir, en cada ocasión será generado un conjunto de datos potencialmente distinto.
Para generar el mismo conjunto de datos puede fijar la semilla con el argumento
random_state :

In [ ]: # No importa cuantas veces lo ejecute, el resultado siempre será el mismo.


X, y = datasets.make_blobs(random_state=42)

plt.scatter(X[:,0], X[:,1], c=y);

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 10/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

2.3. Conjuntos de datos de archivos externos


Scikit-learn permite usar DataFrames de Pandas como entrada para sus algoritmos de
preprocesamiento y aprendizaje computacional.
De aquí en adelante trabajaremos con el conjunto de datos Titanic para realizar
preprocesamiento, entrenamiento y evaluación del desempeño.
A continuación cargamos Titanic como un DataFrame de Pandas usando la función
pd.read_csv . Esta permite leer archivos separados por comas y cargarlos en objetos de
tipo DataFrame .
In [ ]: # Librería de análisis y manipulación de datos Pandas.
import pandas as pd

# Usamos una url remota para cargar nuestro conjunto.


titanic_url = 'https://raw.githubusercontent.com/JuezUN/datasets/master/titanic
titanic_df = pd.read_csv(titanic_url)

Podemos ver información general del conjunto de datos con el método info de Pandas:
In [ ]: titanic_df.info()

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 11/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

El dataset Titanic presenta un problema de clasificación. Se busca predecir la supervivencia


de una persona basada en características como la clase del pasajero ( Pclass ), su género
( Sex ), su edad ( Age ), el número de hermanos y pareja a bordo, ( SibSp ), el número de
padres e hijos a bordo ( Parch ), entre otras.
Para mantener las cosas sencillas eliminaremos la variable Cabin ya que presenta muchos
datos faltantes y luego eliminaremos los registros que contentan valores faltantes en
algunas de las columnas restantes. También eliminaremos la variable PassengerId ya
que es un identificador y no una variable propia de los ejemplos.
In [ ]: df_full = titanic_df.drop(['Cabin', 'PassengerId'], axis=1) # Eliminamos las co
df_nona = df_full.dropna(axis=0) # Eliminamos los ejemplos con valores faltante

print(df_nona.shape)

(712, 10)

Para convertir el DataFrame de Titanic en el formato X, y podemos hacer lo siguiente:


In [ ]: X = df_nona.drop(['Survived'], axis=1) # El conjunto de datos sin la variable o
y = df_nona['Survived'] # La columna de la variable objetivo 'Survived'.

print(X.shape)
print(y.shape)

(712, 9)
(712,)

Podemos ver que el conjunto de datos resultante tiene ejemplos con características.
712 9

No incluimos Survived en X porque es la etiqueta que buscamos predecir. Incluirla sería


un error conocido como filtración de etiquetas, que ocurre cuando se incluyen
características durante el entrenamiento que proporcionan información de la etiqueta (o la
misma etiqueta), que podría no estar disponible durante el tiempo de predicción. Imagínese
entrenar un modelo para predecir la etiqueta, pero necesitarla como dato de entrada.
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 12/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Aunque Scikit-learn acepta objetos convertibles a arreglos de NumPy como DataFrames y


listas. Nos adelantaremos y convertiremos X y y en arreglos de NumPy.
In [ ]: print(type(X))
print(type(y))

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.series.Series'>

Primero veamos como convertir un objeto de Pandas a un arreglo de NumPy, esto se logra
con el atributo values de un DataFrame o una Serie.
In [ ]: # .values retorna un arreglo de NumPy.
y = y.values

Separaremos X en dos arreglos de NumPy. Guardaremos las variables numéricas en


X_numeric y las variables categóricas en X_categoric .

Las variables numéricas del conjunto de datos son: Age , SibSp , Parch , y Fare .
In [ ]: numeric = ['Age', 'SibSp', 'Parch', 'Fare']

# .values retorna un arreglo de NumPy.


X_numeric = X[numeric].values

print(X_numeric.shape)
print(type(X_numeric))

(712, 4)
<class 'numpy.ndarray'>

Veamos algunos ejemplos:


In [ ]: for i in range(5):
print(f'Ejemplo {i}:')
print('Variables:', X_numeric[i])
print('Etiqueta:', y[i])
print()

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 13/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
Ejemplo 0:
Variables: [22. 1. 0. 7.25]
Etiqueta: 0

Ejemplo 1:
Variables: [38. 1. 0. 71.2833]
Etiqueta: 1

Ejemplo 2:
Variables: [26. 0. 0. 7.925]
Etiqueta: 1

Ejemplo 3:
Variables: [35. 1. 0. 53.1]
Etiqueta: 1

Ejemplo 4:
Variables: [35. 0. 0. 8.05]
Etiqueta: 0

Cómo podemos ver, son todas variables numéricas.


Las variables categóricas del conjunto de datos son: Pclass , Name , Sex , Ticket y
Embarked .

Si bien Pclass está almacenada como un tipo numérico, representa una


variable categórica ordinal.
Primero veamos cuántos valores únicos tiene cada una:
In [ ]: categoric = ['Name', 'Ticket', 'Pclass', 'Sex', 'Embarked']

for var in categoric:


print(f'Valores posibles de {var}: \t{X[var].nunique()}')

Valores posibles de Name: 712


Valores posibles de Ticket: 541
Valores posibles de Pclass: 3
Valores posibles de Sex: 2
Valores posibles de Embarked: 3

No tendremos en cuenta Name y Ticket para el siguiente ejemplo por su gran cantidad
de valores posibles. En este caso usaremos solamente las variables Pclass , Sex y
Embarked .

In [ ]: # .values retorna un arreglo de numpy


X_categoric = X[['Pclass', 'Sex', 'Embarked']].values

print(X_categoric.shape)
print(type(X_categoric))

(712, 3)
<class 'numpy.ndarray'>

Veamos algunos ejemplos:


file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 14/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [ ]: ids = [0, 1, 15, 20]

for i in ids:
print(f'Ejemplo {i}:')
print('Variables:', X_categoric[i])
print('Etiqueta:', y[i])
print()

Ejemplo 0:
Variables: [3 'male' 'S']
Etiqueta: 0

Ejemplo 1:
Variables: [1 'female' 'C']
Etiqueta: 1

Ejemplo 15:
Variables: [3 'male' 'Q']
Etiqueta: 0

Ejemplo 20:
Variables: [1 'male' 'S']
Etiqueta: 1

Los valores , y corresponden a la clase de viaje del pasajero de la variable


1 2 3

Pclass .
Los valores male y female corresponden a la variable Sex (género de la persona).
Los valores S , C y Q corresponden a la variable Embarked . Estos indican el puerto
de embarque que utilizó la persona, donde C = Cherbourg , Q = Queenstown y S
= Southampton .

3. Preprocesamiento
Scikit-learn expone el paquete preprocessing el cual contiene una serie de
transformaciones para variables numéricas tanto como categóricas.
La importancia del preprocesamiento radica en que puede potencialmente mejorar (o
empeorar) el desempeño de los algoritmos de aprendizaje computacional. En el caso de
variables categóricas para el caso de Scikit-learn no pueden ser usadas sin aplicar un
preprocesamiento.
Las transformaciones de Scikit-learn son fáciles de usar. Estas implementan la interfaz
Transformer , la cual expone los 3 siguientes métodos:

fit(X) : (del español ajustar) permite aprender un conjunto de parámetros de X que


son necesarios para aplicar la transformación (e.g la media, el mínimo o el máximo, el
número de características, etc).

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 15/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

transform(X) : (del español transformar) aplica el preprocesamiento a X y retorna


X transformado.

fit_transform(X) :(del español ajustar y transformar) aplica fit a X y retorna


X transformado. Es utilizado como un atajo de una línea.

Algunas transformaciones no pueden usar transform sin haber usado fit


previamente. De igual manera algunas transformaciones no necesitan parámetros y fit
no tiene ningún efecto secundario.
En esta ocasión nosotros solo utilizaremos fit_transform para realizar el
preprocesamiento.
3.1. Variables numéricas
En esta ocasión introduciremos métodos de preprocesamiento numéricos bastante
sencillos:
StandardScaler
MinMaxScaler

3.1.1. StandardScaler
Veamos primero StandardScaler :
Este permite aplicar la transformación:
X − μ

X =
σ

Donde:
: Media aritmética de los datos.
μ

: Desviación estándar de los datos.


σ

La transformación produce un nuevo conjunto de datos centrado en y con una desviación 0

estándar de . 1

In [ ]: from sklearn.preprocessing import StandardScaler

scaler = StandardScaler() # Declaramos el Transformer "StandardScaler"


X_numeric_standarized = scaler.fit_transform(X_numeric) # Transformamos la matr

Aunque X_numeric tiene varias características numéricas, la transformación se aplica a


cada una de las columnas de manera independiente.
Veamos algunos ejemplos:
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 16/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [ ]: for i in range(3):
print('Ejemplo:', i)
print('Original: ', X_numeric[i])
print('Estandarizado: ', X_numeric_standarized[i])
print()

Ejemplo: 0
Original: [22. 1. 0. 7.25]
Estandarizado: [-0.52766856 0.52251079 -0.50678737 -0.51637992]

Ejemplo: 1
Original: [38. 1. 0. 71.2833]
Estandarizado: [ 0.57709388 0.52251079 -0.50678737 0.69404605]

Ejemplo: 2
Original: [26. 0. 0. 7.925]
Estandarizado: [-0.25147795 -0.55271372 -0.50678737 -0.50362035]

3.1.2. MinMaxScaler
MinMaxScaler permite escalar los datos a un rango específico, es decir, si una
característica se encuentra en el rango [min(X), max(X)] y el argumento
feature_range = (0, 1) , entonces cada valor será escalado de tal manera que esté
en el rango [0, 1] .
In [ ]: from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1)) # Declaramos el Transformer "MinMa


X_numeric_minmax = scaler.fit_transform(X_numeric) # Transformamos la matriz "X

Al igual que StandardScaler , MinMaxScaler aplica su transformación a cada columna


de manera independiente.
Veamos algunos ejemplos:
In [ ]: for i in range(3):
print('Ejemplo:', i)
print('Original: ', X_numeric[i])
print('MinMax: ', X_numeric_minmax[i])
print()

Ejemplo: 0
Original: [22. 1. 0. 7.25]
MinMax: [0.27117366 0.2 0. 0.01415106]

Ejemplo: 1
Original: [38. 1. 0. 71.2833]
MinMax: [0.4722292 0.2 0. 0.13913574]

Ejemplo: 2
Original: [26. 0. 0. 7.925]
MinMax: [0.32143755 0. 0. 0.01546857]

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 17/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

In [ ]: # Valores mínimo y máximo de todo el dataset.


X_numeric_minmax.min(), X_numeric_minmax.max()

(0.0, 1.0)
Out[ ]:

Como vemos, todos los valores están entre y . 0 1

StandardScaler y MinMaxScaler realizan transformaciones similares y el desempeño


de cada una es dependediente del conjunto de datos.
En nuestro ejemplo usaremos X_numeric_minmax para entrenar nuestros modelos.
3.2. Variables categóricas
Típicamente, los modelos de aprendizaje computacional no aceptan como entrada variables
categóricas las cuales pueden estar representadas con cadenas de texto. Antes de ser
empleadas necesitan ser preprocesadas en valores numéricos.
En esta ocasión veremos un método muy popular conocido como One Hot Encoding.
Emplearemos One Hot Encoding sobre la variable Embarked .
One Hot Encoding codifica una variable con valores posibles enumerados como
n

en un vector de tamaño ; donde la -ésima posición del vector está asociada


1, 2, . . . , n n i

con el -ésimo valor posible.


i

Asumiendo que un ejemplo tiene el -ésimo valor posible de la variable original, se procesa
j

de la siguiente manera:
Se asigna 1 en la posición . j

Se asigna 0 al resto.
La variable Embarked tiene los siguientes valores únicos: S, C, Q .
Lo verificamos con el método unique de NumPy.
In [ ]: import numpy as np

# Obtenemos los valores únicos de un arreglo de NumPy con el método "unique".


print(np.unique(X_categoric[:,2]))

['C' 'Q' 'S']

Por lo tanto, al aplicar One Hot Encoding a los siguientes datos:


Embarked
S
C
Q
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 18/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Embarked
C
Se transformarían de la siguiente manera:
S C Q
1 0 0
0 1 0
0 0 1
0 1 0
One Hot Encoding se puede de usar de manera muy sencilla en Scikit-learn con el método
OneHotEncoder :

Por defecto OneHotEncoder retorna matrices sparse de SciPy, una implementación que
mejora el desempeño de las operaciones matriciales cuando se tienen muchas entradas de
una matriz en (¡perfecto para One Hot Encoding!).
0

Para mantener las cosas sencillas con sparse=False le podemos pedir a


OneHotEncoder que retorne arreglos de NumPy.

In [ ]: from sklearn.preprocessing import OneHotEncoder

enc = OneHotEncoder(sparse=False) # Declaramos el Transformer "OneHotEncode


X_categoric_onehot = enc.fit_transform(X_categoric) # Usamos "fit_transform" pa
print(X_categoric_onehot.shape)
print(type(X_categoric_onehot))

(712, 8)
<class 'numpy.ndarray'>
/usr/local/lib/python3.9/dist-packages/sklearn/preprocessing/_encoders.py:868:
FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will
be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its
default value.
warnings.warn(

Cómo podemos ver, la variable Sex con valores únicos, la variable Pclass con
2 3

valores únicos y la variable Embarked con valores únicos fueron transformadas en


3 8

variables distintas en total.


Veamos algunos ejemplos:
In [ ]: ids = [0, 1, 15, 20]
for i in ids:
print('Ejemplo:', i)
print('Original: ', X_categoric[i])
print('One Hot: ', X_categoric_onehot[i])
print()

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 19/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn
Ejemplo: 0
Original: [3 'male' 'S']
One Hot: [0. 0. 1. 0. 1. 0. 0. 1.]

Ejemplo: 1
Original: [1 'female' 'C']
One Hot: [1. 0. 0. 1. 0. 1. 0. 0.]

Ejemplo: 15
Original: [3 'male' 'Q']
One Hot: [0. 0. 1. 0. 1. 0. 1. 0.]

Ejemplo: 20
Original: [1 'male' 'S']
One Hot: [1. 0. 0. 0. 1. 0. 0. 1.]

Antes de continuar al entrenamiento de modelos de aprendizaje computacional, usando


NumPy juntaremos las variables numéricas preprocesadas y las variables categóricas
preprocesadas en el arreglo X_full . Para esto usaremos la función np.concatenate
para concatenar X_numeric_minmax y X_categoric_onehot a través del axis 1

(columnas).
In [ ]: import numpy as np

X_full = np.concatenate((X_numeric_minmax, X_categoric_onehot),


axis=1) # Concatenamos por el eje vertical (columnas)
print(X_full.shape)

(712, 12)

4. Entrenamiento de modelos
En esta sección entrenaremos dos modelos de regresión logística.
Uno utilizando solo las variables numéricas preprocesadas.
Otro usando las variables numéricas y categóricas, ambas preprocesadas.
Generalmente los algoritmos de aprendizaje computacional son entrenados en una partición
de entrenamiento (train) y probados en una partición de datos de prueba (test).
Los datos de entrenamiento son aquellos datos de los cuales el algoritmo aprende.
Los datos de prueba son aquellos que se usan para estimar el desempeño del
algoritmo en datos desconocidos por el modelo.
Las particiones NO deben compartir datos. Con ayuda de scikit-learn podemos crear
particiones de entrenamiento y prueba para hacer esto fácilmente.
4.1. Partición de entrenamiento y prueba
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 20/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Scikit-learn permite realizar una partición de entrenamiento y prueba fácilmente con la


función train_test_split del paquete model_selection .
train_test_split(X, y) retorna una tupla X_train, X_test, y_train, y_test
donde X_train, X_test son la partición entrenamiento - prueba de X y y_train,
y_test son la partición de entrenamiento y prueba de y .

Tenga en cuenta que usted le puede poner cualquier nombre a las variables que retorna
train_test_split, lo anterior es solo una convención.
Usando el parámetro test_size podemos indicar, con un número entre 0 y 1, el
porcentaje de datos que deseamos usar para la partición de prueba.
Un ejemplo básico de uso, con de los datos para pruebas y para entrenamiento
30% 70%

sería el siguiente:
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.3)

En nuestro caso le indicamos a train_test_split que utilice el de los datos como 30%

datos de prueba y la utilizamos con X_numeric_minmax y X_full .


A continuación importamos train_test_split del submódulo
sklearn.model_selection .

In [ ]: # Submódulo de selección de modelos y partición de datos.


from sklearn.model_selection import train_test_split

Lo usamos sobre X_numeric_minmax :


In [ ]: X_train_num, X_test_num, y_train_num, y_test_num = train_test_split(X_numeric_m
y,
test_size=0
random_stat

Y lo usamos sobre X_full :


In [ ]: X_train_full, X_test_full, y_train_full, y_test_full = train_test_split(X_full,
y,
test_si
random_

train_test_split realiza la partición de manera aleatoria. Para especificar la semilla


aleatoria se puede utilizar el parámetro random_state .
En este caso como tenemos dos versiones de X ( X_numeric_minmax y X_full ) y
hemos utilizado la misma semilla aleatoria en ambos llamados de la función para
asegurarnos que los ejemplos, aún con diferentes características, sean consistentes sobre
cada partición.
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 21/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Podemos verificar que efectivamente se respetó el ordenamiento:


In [ ]: print(all(y_train_num == y_train_full))
print(all(y_test_num == y_test_full))

True
True

Para simplificar las cosas asignamos las variables y_train y y_test :


In [ ]: y_train = y_train_num
y_test = y_test_num

4.2. Entrenamiento
Debido a que el conjunto de datos Titanic plantea un problema de clasificación
(supervivencia), usaremos un modelo de clasificación lineal llamado regresión logística.
Los modelos de clasificación buscan discernir el grupo al que pertenece un ejemplo (i.e.,
predecir su etiqueta).
Un modelo de clasificación recibe un conjunto de variables x (características) y produce
una salida y (etiqueta) la cual es la predicción del modelo.
La salida de un modelo de clasificación suele estar codificada como un número. El numero
está asociado a la clase (que el modelo predice) que pertenece el ejemplo.
Utilizaremos el modelo generado por la función LogisticRegression del paquete
linear_model de Scikit-learn.

En scikit-learn los modelos suelen implementar la interfaz Estimator y Predictor .


Por ahora nos interesa saber que:
Los Estimator implementan fit(X, y) .
Los Predictor implementan predict(X) .
Los modelos deben ser ajustados antes de ser utilizados para realizar cualquier predicción.
En scikit-learn entrenar un modelo de aprendizaje computacional es tan sencillo como
importarlo, crear una instancia y utilizar fit .
Nota: Para nuestro ejemplo, en el entrenamiento utilizaremos cada versión de
X_train ( X_train_num y X_train_full ) con el objetivo de entrenar
dos modelos distintos.

4.3. Clasificador de variables numéricas


file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 22/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Primero entrenaremos una regresión logística utilizando únicamente las variables


numéricas.
Importamos el paquete linear_model que incluye la clase LogisticRegression .
In [ ]: # Submódulo de modelos lineales.
from sklearn import linear_model

Creamos una instancia de LogisticRegression y la guardamos en la variable


clf_numeric .

In [ ]: clf_numeric = linear_model.LogisticRegression(random_state=2)

clf_numeric no ha sido entrenado, para esto usamos el método fit con:


X_train_num : matriz con las características numéricas de la partición de
entrenamiento.
y_train : vector con las etiquetas de la partición de entrenamiento.

In [ ]: clf_numeric.fit(X_train_num, y_train)

Out[ ]: ▾ LogisticRegression
LogisticRegression(random_state=2)

4.4. Clasificador de variables numéricas y categóricas


Para entrenar un segundo modelo con todas las variables numéricas y categóricas
(preprocesadas) realizamos los siguientes pasos:
Creamos una instancia y la guardamos en clf_full :
In [ ]: # Variables numéricas y categóricas.
clf_full = linear_model.LogisticRegression(random_state=2)

Utilizamos fit pero esta vez con X_train_full , la matriz con las características tanto
numéricas como categóricas de la partición de entrenamiento.
In [ ]: clf_full.fit(X_train_full, y_train)

Out[ ]: ▾ LogisticRegression

LogisticRegression(random_state=2)

5. Evaluación
file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 23/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Calcularemos la exactitud y el error de cada clasificador entrenado en la partición de


entrenamiento y prueba.
La exactitud se define como:
#ejemplos clasificados correctamente
exactitud =
#ejemplos

y el error se define como:


error = 1.0 − exactitud

En scikit-learn la exactitud se puede calcular mediante la función accuracy_score del


paquete metrics .
Nota: Se profundizará en distintas métricas de rendimiento en las unidades
siguientes.
A continuación, importamos accuracy_score .
In [ ]: from sklearn.metrics import accuracy_score

5.1. Clasificador variables numéricas


Primero veamos algunas predicciones del modelo sobre la partición de prueba, para esto
debemos usar el método predict :
In [ ]: # El método predict se debería utilizar sobre un clasificador entrenado previam

y_pred = clf_numeric.predict(X_test_num) # Retorna un arreglo con la predicción

for i in range(5):
print(f'Predicho: {y_pred[i]}, Etiqueta: {y_test[i]}\n')

Predicho: 0, Etiqueta: 1

Predicho: 0, Etiqueta: 1

Predicho: 0, Etiqueta: 0

Predicho: 1, Etiqueta: 1

Predicho: 0, Etiqueta: 0

Como podemos ver, el modelo se equivoca en de los primeros ejemplos. Note que
2 5

predict acepta una matriz, un error común es tratar de usar predict con vectores.

Para calcular la exactitud sobre toda la partición de prueba utilizamos accuracy_score :

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 24/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

accuracy_score recibe como primer parámetro las etiquetas reales y


como segundo parámetro las etiquetas predichas.
In [ ]: # Obtenemos la predicción del clasificador usando variables numéricas.
y_pred = clf_numeric.predict(X_test_num)

# Calculamos la exactitud de la predicción.


acc = accuracy_score(y_test, y_pred)

print(f'Exactitud en prueba {acc}')


print(f'Error en prueba: {1.0 - acc}')

Exactitud en prueba 0.6121495327102804


Error en prueba: 0.3878504672897196

Veamos las métricas sobre la partición de entrenamiento:


In [ ]: # Obtenemos la predicción del clasificador usando tanto variables numéricas com
y_pred = clf_numeric.predict(X_train_num)

# Calculamos la exactitud de la predicción.


acc = accuracy_score(y_train, y_pred)

print(f'Exactitud en entrenamiento {acc}')


print(f'Error en entrenamiento: {1.0 - acc}')

Exactitud en entrenamiento 0.6485943775100401


Error en entrenamiento: 0.3514056224899599

Como podemos ver, el error de entrenamiento es menor que el error de prueba.


5.2. Clasificador variables numéricas y categóricas
Veamos las métricas sobre la partición de entrenamiento:
In [ ]: y_pred = clf_full.predict(X_train_full)
acc = accuracy_score(y_train, y_pred)

print(f'Exactitud en entrenamiento {acc}')


print(f'Error en entrenamiento: {1.0 - acc}')

Exactitud en entrenamiento 0.8012048192771084


Error en entrenamiento: 0.1987951807228916

Veamos las métricas sobre la partición de prueba:


In [ ]: y_pred = clf_full.predict(X_test_full)
acc = accuracy_score(y_test, y_pred)

print(f'Exactitud en prueba {acc}')


print(f'Error en prueba: {1.0 - acc}')

Exactitud en prueba 0.7757009345794392


Error en prueba: 0.22429906542056077

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 25/26
4/5/23, 18:44 M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn

Cómo podemos ver incluir las variables categóricas mejora el desempeño del mismo
algoritmo substancialmente.
En Titanic un indicador de supervivencia muy importante es el género, una proporción
mucho más grande de mujeres que de hombres sobrevivieron a la tragedia. Esto puede ser
utilizado para interpretar la mejora de desempeño del modelo.

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn:
Scikit-learn - Datasets
Scikit-learn - Preprocessing
Scikit-learn - Linear models

Créditos
Profesor: Fabio Augusto Gonzalez
Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería
In [ ]: !jupyter nbconvert --to html /content

file:///Users/mac/Downloads/M2U1_Introducción_al_Aprendizaje_Computacional_y_scikit_learn.html 26/26
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Complejidad y sobreajuste
En este notebook exploraremos la diferencia entre parámetros e hiperparámetros de un
modelo de aprendizaje computacional, además de conceptos como la capacidad o
complejidad, y las consecuencias del subajuste y sobreajuste.
Para esto, observaremos cómo se comporta la complejidad del modelo de clasificación no
lineal k-vecinos más cercanos (K-nearest neighbors en inglés).

1. Dependencias
Importamos las librerías necesarias y definimos algunas funciones básicas de visualización
que vamos a usar en algunos ejemplos.
1.1. Dependencias
Para la construcción de modelos y ejecución de procedimientos metodológicos de
aprendizaje automático, utilizaremos la librería Scikit-learn ( sklearn ) y varias de sus
funciones y conjuntos de datos.
In [ ]: # Actualizamos scikit-learn a la última versión
!pip install -U scikit-learn

# Importamos scikit-learn
import sklearn

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 1/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-p
ackages (1.2.2)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn) (3.1.0)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.22.4)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-p
ackages (from scikit-learn) (1.10.1)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.1.1)

In [ ]: # Librerías básicas de análisis y visualización de datos.


import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

In [ ]: # Configuraciones para las librerías y módulos usados

# Ignoramos las advertencias o warnings.


import warnings
warnings.simplefilter(action='ignore')

# Configuramos el formato por defecto de la


# librería de visualización Matplotlib.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

Este material se realizó con las siguientes versiones:


Python: 3.7.10
Scikit-learn: 0.24.1
NumPy: 1.19.5
Pandas: 1.1.5
Matplotlib: 3.2.2
Seaborn: 0.11.1
In [ ]: # Versión de Python y las demás librerías.
!python --version
print('Scikit-learn', sklearn.__version__)
print('NumPy', np.__version__)
print('Pandas', pd.__version__)
print('Matplotlib', mpl.__version__)
print('Seaborn', sns.__version__)

Python 3.9.16
Scikit-learn 1.2.2
NumPy 1.22.4
Pandas 1.4.4
Matplotlib 3.7.1
Seaborn 0.12.2

1.2. Funciones de utilidad y visualización


file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 2/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Para ilustrar los ejemplos discutidos en este material utilizaremos algunas funciones que
permiten visualizar de manera general los datos y conceptos discutidos en las secciones.
Nota: Matplotlib y Seaborn se encuentran por fuera del alcance de este
módulo. No es necesario que entienda estas funciones en detalle para sacar
partido del resto del contenido puesto a su disposición. Usted decide si leer o
no estas funciones en profundidad. Si decide omitir esta sección, continúe
directamente con la siguiente sección, en donde se discutirán los conjuntos
de datos que vamos a utilizar.
In [ ]: # Función para visualizar un conjunto de datos de dos variables en un plano 2D
def plot_data(X, y, model = None, ax = None, title=None):

if ax is None:
_, ax = plt.subplots(dpi = 110)

if model is not None:


pred_fun = gen_pred_fun(model)
plot_decision_region(X, pred_fun, ax)

y_unique = np.unique(y)
df = pd.DataFrame({'x1': X[:,0], 'x2': X[:,1], 'Clases': y})
sns.set_theme()
sns.scatterplot(data = df, x = 'x1', y = 'x2',
hue = 'Clases',style = 'Clases', ax = ax, palette = 'Set1')

In [ ]: # Función para visualizar la superficie de decisión de un clasificador


def plot_decision_region(X, pred_fun, ax=None):
min_x, max_x = np.min(X[:, 0]), np.max(X[:, 0])
min_y, max_y = np.min(X[:, 1]), np.max(X[:, 1])

min_x = min_x - (max_x - min_x) * 0.05


max_x = max_x + (max_x - min_x) * 0.05
min_y = min_y - (max_y - min_y) * 0.05
max_y = max_y + (max_y - min_y) * 0.05

x_vals = np.linspace(min_x, max_x, 100)


y_vals = np.linspace(min_y, max_y, 100)

XX, YY = np.meshgrid(x_vals, y_vals)


grid_r, grid_c = XX.shape

ZZ = np.zeros((grid_r, grid_c))

for i in range(grid_r):
for j in range(grid_c):
ZZ[i, j] = pred_fun(XX[i, j], YY[i, j])

ax.contourf(XX, YY, ZZ, 100, cmap = plt.cm.coolwarm_r, vmin= -1, vmax=2, al


ax.set_xlabel("x")
ax.set_ylabel("y")

In [ ]: # Función para visualizar la curva de aprendizaje a partir


# del error de entrenamiento y de generalización.

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 3/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste
def plot_learning_curve(train_error, generalization_error):

balance_point = np.array(generalization_error).argmin() + 1
plt.figure(figsize = (8, 5), dpi = 105)

plt.plot(range(1, k_values + 1), train_error, label="Entrenamiento")


plt.plot(range(1, k_values + 1), generalization_error, label="Validación")
plt.xticks(range(0, k_values + 1, 5))
plt.xlabel("k-vecinos")
plt.ylabel("Error")
y_min, y_max = plt.gca().get_ylim()
plt.vlines(balance_point, y_min, y_max, colors = ['red'], linestyles = ['dash
plt.ylim([y_min, y_max])
plt.text(balance_point + 1, 0.165, 'Punto de balance')
plt.legend();

In [ ]: #Función para generar la función de predicción de un clasificador entrenado pre


def gen_pred_fun(clf):
def pred_fun(x1, x2):
x = np.array([[x1, x2]])
return clf.predict(x)[0]
return pred_fun

2. Definición del conjunto de datos


En este material vamos a trabajar con un conjunto de datos artificial. El conjunto es creado
usando la función make_moons de Scikit-Learn. Esta función permite introducir algo de
ruido sobre las muestras creadas con el argumento noise .
In [ ]: from sklearn import datasets

X, y = datasets.make_moons(n_samples=1000, noise=0.4, random_state=0)

In [ ]: print('X ~ n_muestras x n_características:', X.shape)


print('y ~ n_muestras:', y.shape)

print('\nPrimeras 5 muestras:\n', X[:5, :])


print('\nPrimeras 5 etiquetas:', y[:5])

X ~ n_muestras x n_características: (1000, 2)


y ~ n_muestras: (1000,)

Primeras 5 muestras:
[[ 2.17300137 0.68579369]
[ 1.97315415 0.28555497]
[-0.15360193 0.194076 ]
[-0.55760668 0.20288223]
[ 0.32028022 -0.44518473]]

Primeras 5 etiquetas: [1 1 0 1 1]

In [ ]: plot_data(X, y)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 4/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

A continuación, vamos a dividir el conjunto en para entrenamiento y para prueba.


70% 30%

Utilizaremos el parámetro stratify para estratificar en submuestras balanceadas.


In [ ]: from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y,


test_size=0.3,
random_state=1234,
stratify=y)

Vamos a verificar el número de muestras de ambas particiones y la distribución de clases


de cada una.
In [ ]: print(f'Número de muestras en entrenamiento: {X_train.shape[0]}')
print(f'Número de muestras en prueba: {X_test.shape[0]}')
print(f'Número de características: {X_train.shape[1]}')

print(f'Distribución de clases en entrenamiento: {np.bincount(y_train)}')


print(f'Distribución de clases en prueba: {np.bincount(y_test)}')

Número de muestras en entrenamiento: 700


Número de muestras en prueba: 300
Número de características: 2
Distribución de clases en entrenamiento: [350 350]
Distribución de clases en prueba: [150 150]

3. Parámetros e Hiperparámetros
file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 5/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Los parámetros o pesos de un modelo se refieren a los valores que caracterizan un


modelo. Por ejemplo, en una regresión lineal los parámetros del modelo corresponden a los
coeficientes por los cuales se multiplican las variables de entrada. Los parámetros son
aprendidos por el algoritmo de aprendizaje a partir de los datos.
Por otra parte, los hiperparámetros son todos los parámetros que no se aprenden y que
controlan el aprendizaje y el comportamiento del modelo. Estos son especificados de forma
manual por el programador en la concepción del modelo.
Para entender la diferencia entre parámetros e hiperparámetros vamos a ver un ejemplo con
uno de los modelos de clasificación más simples: la regresión logística.
El modelo de regresión logística busca clasificar los datos de forma binaria en dos
categorías ( y ). Para ello, usa la función logística o sigmoidal:
0 1

1
^ =
y
1 + e−w⋅x+w0

Donde es la predicción, y son los parámetros y es una observación o un vector de


^
y w w0 x

características que representa cada ejemplo.


Ahora vamos a definir un modelo de regresión logística en Scikit-Learn:
In [ ]: # Importamos el modelo de sklearn.
from sklearn.linear_model import LogisticRegression

In [ ]: # Definimos el modelo.
model_1 = LogisticRegression()

Hasta este punto ya tenemos definido el modelo, podríamos pasar a entrenarlo y


posteriormente obtener predicciones sobre datos nuevos. No obstante, podemos definir
otro modelo de regresión logística con algunas variaciones:
In [ ]: # Definimos el segundo modelo
model_2 = LogisticRegression(fit_intercept=False)

Aunque el modelo model_2 es un modelo de regresión logística, su comportamiento va a


ser diferente al tener una variación en el hiperparámetro fit_intercept . Este especifica
si el modelo va a tener en cuenta el intercepto . Este segundo modelo se describiría por
w0

medio de la siguiente ecuación:


1
^ =
y
−w⋅x
1 + e

Este hiperparámetro afecta la forma del modelo, ya que tiene un parámetro menos.
Veamos la diferencia entre el comportamiento de los dos modelos definidos en el conjunto
de datos artificial blobs:

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 6/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

In [ ]: X_blobs, y_blobs = datasets.make_blobs(n_samples = 200, centers = 2, random_st

In [ ]: # Entrenamos el modelo 1.

model_1.fit(X_blobs, y_blobs)

Out[ ]: ▾ LogisticRegression

LogisticRegression()

In [ ]: # Entrenamos el modelo 2.

model_2.fit(X_blobs, y_blobs)

Out[ ]: ▾ LogisticRegression
LogisticRegression(fit_intercept=False)

Podemos ver los parámetros de los dos modelos. Estos pueden ser accedidos con los
atributos coef_ (vector de pesos del modelo ) e intercept_ (intercepto ).
w w0

In [ ]: # Vector w de pesos e intercepto w_0 del modelo 1.


print(f"Pesos w: {model_1.coef_}")
print(f"Intercepto w_0: {model_1.intercept_}")

Pesos w: [[ 0.94962826 -1.54776072]]


Intercepto w_0: [6.36160649]

In [ ]: # Vector w de pesos e intercepto w_0 del modelo 2.


print(f"Pesos w: {model_2.coef_}")
print(f"Intercepto w_0: {model_2.intercept_}")

Pesos w: [[ 2.08605019 -0.83988412]]


Intercepto w_0: [0.]

Como podemos ver, el segundo modelo tiene y esto afecta los valores del vector de
w0 = 0

pesos . Así mismo, afecta todo el aprendizaje y los resultados obtenidos.


w

A partir de la siguiente visualización podemos ver que la línea que separa las regiones de
decisión del modelo pasa por el punto cuando no se considera el intercepto en el
(0, 0)

entrenamiento del modelo.


In [ ]: fig, axes = plt.subplots(ncols = 2, dpi = 110, figsize = (10, 4))

plot_data(X_blobs, y_blobs, model_1, ax = axes[0],


title = f'Regresión CON intercepto ($w_0$ = {model_1.intercept_[0]:.2
plot_data(X_blobs, y_blobs, model_2, ax = axes[1],
title = f'Regresión SIN intercepto ($w_0$ = {model_2.intercept_[0]:.2

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 7/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

En general, en Scikit-Learn los hiperparámetros se encuentran entre los argumentos


requeridos para la definición del modelo. Veamos los argumentos de la regresión logística:
In [ ]: model_1.get_params()

{'C': 1.0,
Out[ ]:
'class_weight': None,
'dual': False,
'fit_intercept': True,
'intercept_scaling': 1,
'l1_ratio': None,
'max_iter': 100,
'multi_class': 'auto',
'n_jobs': None,
'penalty': 'l2',
'random_state': None,
'solver': 'lbfgs',
'tol': 0.0001,
'verbose': 0,
'warm_start': False}

Encontramos algunos hiperparámetros como:


fit_intercept : especifica si el modelo utiliza el intercepto . w0

class_weight permite ponderar cada clase de acuerdo a un peso determinado.


penalty : función de pérdida usada para la regularización del modelo. Es una
restricción que se agrega sobre la función de pérdida que permite optimizar el modelo
de regresión logística.
tol : tolerancia mínima en la función de pérdida para detener el entrenamiento.
solver : algoritmo de optimización que se utilizará para el entrenamiento.

Finalmente, veamos el efecto de variar el parametro class_weight con diferentes valores


en la regresión logística:
In [ ]: # Definimos una serie de valores para el peso de la clase 0.
weights = np.linspace(0, 1, 5)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 8/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste
weights

array([0. , 0.25, 0.5 , 0.75, 1. ])


Out[ ]:

In [ ]: # Definimos una figura con 5 ejes


fig, ax = plt.subplots(nrows = 5, figsize=(6, 20), dpi = 110)

# Iteramos para cada peso a explorar


for i, weight in enumerate(weights):
# Definimos un modelo para el valor de 'class_weight' actual.
# El parámetro 'class_weight' es recibido en
# forma de diccionario con el peso dado por
# cada clase en 'y'.
model = LogisticRegression(class_weight= {0: weight, 1: 1 - weight})

# Entrenamos el modelo
model.fit(X_train, y_train)
# Mostramos la región de decisión y los puntos con sus etiquetas.
plot_data(X_train, y_train, model = model, ax=ax[i], title=f"Pesos por clas

fig.tight_layout()

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 9/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 10/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 11/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

¿Observa alguna diferencia?


Conforme los pesos varían en el rango la región de decisión tiende a predecir una
[0, 1]

mayor cantidad de datos para la clase con mayor peso.

4. Capacidad de un modelo
La capacidad o complejidad de un modelo es su habilidad de ajustarse a una amplia
variedad de funciones.
Una forma de controlar la capacidad de un algoritmo de aprendizaje es elegir su espacio de
hipótesis, el conjunto de funciones que puede seleccionar como la solución.
Por ejemplo, en un modelo de regresión polinomial la capacidad se puede controlar
especificando el máximo grado del polinomio que se puede aprender. A mayor grado
d d

más grande el espacio de hipótesis y por lo tanto mayor capacidad.

En el lado izquierdo de la gráfica, ambos errores son altos, esta es la zona que corresponde
al subajuste. A medida que aumentamos la capacidad, el error de entrenamiento se reduce,
file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 12/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

pero la brecha de generalización aumenta.


Eventualmente, el tamaño de la brecha supera la disminución del error de entrenamiento, y
se entra a la zona de sobreajuste, donde la capacidad es muy alta, por encima de la
capacidad óptima.

5.apropiado.
Subajuste, Sobreajuste y ajuste
Un algoritmo de aprendizaje automático busca cumplir los siguientes objetivos:
Hacer que el error de entrenamiento sea bajo.
Hacer que la brecha entre el error de entrenamiento y el error de prueba sea pequeña.
Dependiendo del desempeño del algoritmo en estas dos habilidades se habla de que el
modelo resultante tiene subajuste, sobreajuste o ajuste apropiado.

6.1 Sobreajuste

El sobreajuste (overfitting en inglés) ocurre cuando la brecha entre el error de


entrenamiento y el error de prueba es muy grande. En estos casos el error de
entrenamiento es bajo, pero el error de generalización es alto. Este resultado indica que el
modelo se sobreajusta a los detalles más granulares de los datos de entrenamiento.
Para ejemplificar este concepto, utilizaremos el algoritmo K-vecinos más cercanos (KNN).
Usamos la partición de entrenamiento y prueba creada y analizaremos un modelo KNN
entrenado con .
k = 1

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 13/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

In [ ]: from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsClassifier
KNeighborsClassifier(n_neighbors=1)

In [ ]: plot_data(X_train, y_train, model = knn)

In [ ]: print("Error en entrenamiento:", 1 - knn.score(X_train, y_train))

Error en entrenamiento: 0.0

¿Tiene sentido que el error sea del ? 0%

Revisemos el desempeño en la partición de prueba:


In [ ]: plot_data(X_test, y_test, model = knn)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 14/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

In [ ]: print('Error en prueba:', 1 - knn.score(X_test, y_test))

Error en prueba: 0.21333333333333337

Podemos observar que cuando el número de vecinos es , el modelo se ajusta demasiado al


1

ruido de los datos de entrada y por lo tanto sufre de sobreajuste.

6.2 Subajuste

El subajuste (underfitting en inglés) ocurre cuando el modelo no logra conseguir un error de


entrenamiento (ni de generalización) suficientemente bajo.
Este resultado ocurre porque el modelo no tiene la capacidad suficiente para capturar la
estructura de los datos y el problema.
file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 15/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Usaremos la partición creada y analizaremos un modelo KNN entrenado con .


k = 400

In [ ]: knn = KNeighborsClassifier(n_neighbors=400)
knn.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsClassifier

KNeighborsClassifier(n_neighbors=400)

In [ ]: plot_data(X_train, y_train, model = knn)

In [ ]: print('Error en entrenamiento:', 1-knn.score(X_train, y_train))

Error en entrenamiento: 0.18999999999999995

Observamos que el error en entrenamiento es del . El modelo entrenado es ahora


19%

demasiado simple y no se puede ajustar a la estructura de los datos.


Ahora medimos el error de generalización del modelo entrenado y visualizamos la
clasificación de los datos de prueba.
In [ ]: plot_data(X_test, y_test, model = knn)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 16/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

In [ ]: print('Error de generalización:', 1 - knn.score(X_test, y_test))

Error de generalización: 0.18999999999999995

Podemos observar que cuando aumentamos el número de vecinos, nuestro modelo sufre de
subajuste. La superficie de decisión se suaviza, pero no logra captar los detalles de los
datos. Tanto el error de entrenamiento como el error de generalización se acercan a . 19%

¿Cómo estimar un buen número de -vecinos más cercanos de manera que el modelo
k

no sobreajuste ni subajuste los datos?


6.3 Ajuste Apropiado

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 17/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

El ajuste apropiado ocurre cuando tanto el error de entrenamiento y el de generalización


son bajos. En este caso el modelo logra comportarse de manera similar tanto en el conjunto
de entrenamiento como en el conjunto de prueba con un desempeño es satisfactorio.
En la siguiente subsección veremos un ejemplo de cómo encontrar el valor de para k

encontrar la complejidad óptima.


6.3.1. Determinación de la complejidad óptima para KNN
Un modelo de aprendizaje de máquina puede ser tan complejo como para recordar las
particularidades y el ruido del conjunto de entrenamiento (sobreajuste), así como puede
ser demasiado flexible para no modelar la variabilidad de los datos (subajuste). El modelo
debe garantizar un compromiso entre el sobreajuste y el subajuste, lo cual se logra
evaluando la complejidad del modelo. Una forma de evaluar la complejidad es analizar el
error de entrenamiento y generalización para diferentes modelos que varían en su
complejidad. En el caso de KNearestNeighbor , la complejidad está determinada por el
número de vecinos . Entre menor sea el número de vecinos, más complejo es el
k

modelo.
A continuación, exploramos un conjunto de valores , con el objetivo de encontrar aquél
k

modelo con el mejor compromiso entre error de entrenamiento y error de generalización.


In [ ]: k_values = 50

Evaluamos el error de entrenamiento y generalización para diferentes valores de


complejidad del modelo:
In [ ]: train_error = []
generalization_error = []

for nn in range(1, k_values + 1):


knn = KNeighborsClassifier(n_neighbors=nn)
knn.fit(X_train, y_train)
train_error.append(1 - knn.score(X_train, y_train))
generalization_error.append(1 - knn.score(X_test, y_test))

Visualizamos ambas curvas de aprendizaje.


In [ ]: plot_learning_curve(train_error, generalization_error)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 18/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Se observa que el error de entrenamiento es más alto para modelos más simples (valor de k

alto) y tiende a cero para modelos más complejos (valor bajo de ). Además, el error de k

validación es alto en ambos extremos, y tiene su punto de balance mínimo en . k = 13

In [ ]: print("Error de generalización con k = 13:", generalization_error[12])

Error de generalización con k = 13: 0.1333333333333333

Tenga en cuenta que a comparación de la gráfica en la sección 4, está gráfica está


invertida, pues, KNN tiene mayor complejidad con menor número de vecinos a considerar.
A continuación, se muestra la frontera de decisión para el clasificador con número de
vecinos .
k = 13

In [ ]: knn = KNeighborsClassifier(n_neighbors=13)
knn.fit(X_train, y_train)

plot_data(X_test, y_test, knn)

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 19/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn en la
evaluación de la complejidad de sus modelos, además de material de apoyo teórico para
reforzar estos conceptos:
Scikit-learn - Underfitting vs Overfitting
Machine Learning Mastery - Overfitting and Underfitting with machine learning
algorithms
Deep Learning Book - Chapter 5 - Machine Learning Basics
Elite Data Science - Overfitting in Machine Learning

Créditos
Profesor: Fabio Augusto González
Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería
file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 20/21
7/5/23, 10:02 M2U2_Exploración_de_la_complejidad_y_el_sobreajuste

In [1]: !jupyter nbconvert --to html /content

[NbConvertApp] Converting notebook /content to html


Traceback (most recent call last):
File "/usr/local/bin/jupyter-nbconvert", line 8, in <module>
sys.exit(main())
File "/usr/local/lib/python3.10/dist-packages/jupyter_core/application.py",
line 277, in launch_instance
return super().launch_instance(argv=argv, **kwargs)
File "/usr/local/lib/python3.10/dist-packages/traitlets/config/application.p
y", line 992, in launch_instance
app.start()
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", li
ne 423, in start
self.convert_notebooks()
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", li
ne 597, in convert_notebooks
self.convert_single_notebook(notebook_filename)
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", li
ne 560, in convert_single_notebook
output, resources = self.export_single_notebook(
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", li
ne 488, in export_single_notebook
output, resources = self.exporter.from_filename(
File "/usr/local/lib/python3.10/dist-packages/nbconvert/exporters/exporter.p
y", line 188, in from_filename
with open(filename, encoding="utf-8") as f:
IsADirectoryError: [Errno 21] Is a directory: '/content'

file:///Users/mac/Downloads/M2U2_Exploración_de_la_complejidad_y_el_sobreajuste.html 21/21
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Evaluación del desempeño


aprendizaje computacional de modelos de
En este notebook se discutirán los distintos métodos y métricas de evaluación del
desempeño en modelos de clasificación (binaria y multiclase) y regresión. Además, se
discutirá el algoritmo de K-vecinos más cercanos para clasificación y regresión en los
conjuntos de datos Iris y Boston.

1. Dependencias
Importamos las librerías necesarias y definimos algunas funciones básicas de visualización
que vamos a usar en algunos ejemplos.
1.1. Dependencias
Para la construcción de modelos y ejecución de procedimientos metodológicos de
aprendizaje automático, utilizaremos la librería Scikit-learn ( sklearn ) y varias de sus
funciones y conjuntos de datos.
In [ ]: !pip install --upgrade matplotlib mlxtend

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 1/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: matplotlib in /usr/local/lib/python3.9/dist-pac
kages (3.7.1)
Requirement already satisfied: mlxtend in /usr/local/lib/python3.9/dist-packag
es (0.21.0)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.9/dis
t-packages (from matplotlib) (23.0)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.
9/dist-packages (from matplotlib) (2.8.2)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.9/dist-
packages (from matplotlib) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/di
st-packages (from matplotlib) (3.0.9)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.9/d
ist-packages (from matplotlib) (4.39.2)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.9/di
st-packages (from matplotlib) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.9/dist-p
ackages (from matplotlib) (0.11.0)
Requirement already satisfied: numpy>=1.20 in /usr/local/lib/python3.9/dist-pa
ckages (from matplotlib) (1.22.4)
Requirement already satisfied: importlib-resources>=3.2.0 in /usr/local/lib/py
thon3.9/dist-packages (from matplotlib) (5.12.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.9/d
ist-packages (from matplotlib) (1.4.4)
Requirement already satisfied: scikit-learn>=1.0.2 in /usr/local/lib/python3.
9/dist-packages (from mlxtend) (1.2.2)
Requirement already satisfied: scipy>=1.2.1 in /usr/local/lib/python3.9/dist-p
ackages (from mlxtend) (1.10.1)
Requirement already satisfied: joblib>=0.13.2 in /usr/local/lib/python3.9/dist
-packages (from mlxtend) (1.1.1)
Requirement already satisfied: pandas>=0.24.2 in /usr/local/lib/python3.9/dist
-packages (from mlxtend) (1.4.4)
Requirement already satisfied: setuptools in /usr/local/lib/python3.9/dist-pac
kages (from mlxtend) (67.6.0)
Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/dist-pa
ckages (from importlib-resources>=3.2.0->matplotlib) (3.15.0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/dist-p
ackages (from pandas>=0.24.2->mlxtend) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/dist-packa
ges (from python-dateutil>=2.7->matplotlib) (1.16.0)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn>=1.0.2->mlxtend) (3.1.0)

In [ ]: # Actualizamos scikit-learn a la última versión


!pip install -U scikit-learn

# Importamos scikit-learn
import sklearn

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 2/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-p
ackages (1.2.2)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.1.1)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn) (3.1.0)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-p
ackages (from scikit-learn) (1.10.1)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.22.4)

In [ ]: # Librerías básicas de análisis y visualización de datos.


import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns # Librería de visualización de datos estadísticos.
import mlxtend # Librería de utilidades de aprendizaje computacional.

In [ ]: # Configuraciones para las librerías y módulos usados

# Ignoramos las advertencias o warnings.


import warnings
warnings.simplefilter(action='ignore')

# Configuramos el formato por defecto de la


# librería de visualización Matplotlib.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

Este material se realizó con las siguientes versiones:


Python: 3.7.10
Scikit-learn: 0.24.1
NumPy: 1.19.5
Pandas: 1.1.5
Matplotlib: 3.2.2
Seaborn: 0.11.1
In [ ]: # Versión de Python y las demás librerías.
!python --version
print('Scikit-learn', sklearn.__version__)
print('NumPy', np.__version__)
print('Pandas', pd.__version__)
print('Matplotlib', mpl.__version__)
print('Seaborn', sns.__version__)

Python 3.9.16
Scikit-learn 1.2.2
NumPy 1.22.4
Pandas 1.4.4
Matplotlib 3.7.1
Seaborn 0.12.2

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 3/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

1.2. Funciones de utilidad y visualización


Para ilustrar los ejemplos discutidos en este material utilizaremos algunas funciones que
permiten visualizar de manera general los datos y conceptos discutidos en las secciones.
Nota: Matplotlib, Seaborn y mlxtend se encuentran por fuera del alcance de
este módulo. No es necesario que entienda estas funciones en detalle para
sacar partido del resto del contenido puesto a su disposición. Usted decide si
leer o no estas funciones en profundidad. Si decide omitir esta sección,
continúe directamente con la siguiente sección, en donde se discutirán los
conjuntos de datos que vamos a utilizar.
In [ ]: from mlxtend.plotting import plot_decision_regions

# Función para visualizar la superficie de decisión de un clasificador.


def plot_decision_region(X, y, clf, classes, title = ""):
fig, ax = plt.subplots(dpi = 120)
plot_decision_regions(X, y, clf = clf, ax = ax)
handles, _ = ax.get_legend_handles_labels()
ax.legend(handles, classes)
ax.set_title(title)
fig.show()

In [ ]: def list_confusion_matrix(cm,classes):
df = pd.DataFrame(data = cm,
index = pd.MultiIndex.from_product([['Valor real'], classes
columns = pd.MultiIndex.from_product([['Valor predicho'], c

return df

2. Conjuntos de datos
Como se mencionó, utilizaremos el conjunto de datos Iris para los ejemplos de clasificación
binaria y clasificación con varias clases y el conjunto de datos Boston para los ejemplos de
regresión.
2.1. Conjunto de datos Iris
Para el problema de clasificación binaria solo consideraremos etiquetas y para 2

clasificación con varias clases usaremos las etiquetas del conjunto de datos Iris.En está
3

sección cargaremos el conjunto de datos y haremos una pequeña exploración de datos


inicial.
Cargamos Iris del paquete sklearn.datasets :
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 4/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

In [ ]: # Loader del conjunto de datos Iris.


from sklearn import datasets

iris = datasets.load_iris()

Podemos observar que contiene este objeto que nos regresa Scikit-learn usando el atributo
keys() :

In [ ]: iris.keys()

dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_name


Out[ ]:
s', 'filename', 'data_module'])

Con la llave DESCR podemos acceder a una descripción general del conjunto de datos:
In [ ]: print(iris.DESCR)

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 5/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
.. _iris_dataset:

Iris plants dataset


--------------------

**Data Set Characteristics:**

:Number of Instances: 150 (50 in each of three classes)


:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica

:Summary Statistics:

============== ==== ==== ======= ===== ====================


Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width: 0.1 2.5 1.20 0.76 0.9565 (high!)
============== ==== ==== ======= ===== ====================

:Missing Attribute Values: None


:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988

The famous Iris database, first used by Sir R.A. Fisher. The dataset is taken
from Fisher's paper. Note that it's the same as in R, but not as in the UCI
Machine Learning Repository, which has two wrong data points.

This is perhaps the best known database to be found in the


pattern recognition literature. Fisher's paper is a classic in the field and
is referenced frequently to this day. (See Duda & Hart, for example.) The
data set contains 3 classes of 50 instances each, where each class refers to a
type of iris plant. One class is linearly separable from the other 2; the
latter are NOT linearly separable from each other.

.. topic:: References

- Fisher, R.A. "The use of multiple measurements in taxonomic problems"


Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions to
Mathematical Statistics" (John Wiley, NY, 1950).
- Duda, R.O., & Hart, P.E. (1973) Pattern Classification and Scene Analysi
s.
(Q327.D83) John Wiley & Sons. ISBN 0-471-22361-1. See page 218.
- Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System
Structure and Classification Rule for Recognition in Partially Exposed
Environments". IEEE Transactions on Pattern Analysis and Machine
Intelligence, Vol. PAMI-2, No. 1, 67-71.
- Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule". IEEE Transaction
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 6/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
s
on Information Theory, May 1972, 431-433.
- See also: 1988 MLC Proceedings, 54-64. Cheeseman et al"s AUTOCLASS II
conceptual clustering system finds 3 classes in the data.
- Many, many more ...

Encontramos que el número de muestras y características de data es:


In [ ]: n_samples, n_features = iris.data.shape

print('Número de muestras:', n_samples)


print('Número de características:', n_features)

Número de muestras: 150


Número de características: 4

Observamos que cada fila corresponde a un ejemplar de una especie de flor. Cada flor tiene
asociado una serie de características, como el ancho y largo del sépalo, y el ancho y largo
del pétalo.

In [ ]: for var, value in zip(iris.feature_names, iris.data[-1]):


print(f"Variable {var}: {value}")

Variable sepal length (cm): 5.9


Variable sepal width (cm): 3.0
Variable petal length (cm): 5.1
Variable petal width (cm): 1.8

Las especies que tratamos de predecir están almacenadas en el atributo target_names :

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 7/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

In [ ]: print(iris.target_names)

['setosa' 'versicolor' 'virginica']

La distribución de las etiquetas es uniforme en el conjunto de datos, es decir, existe el


mismo número (50) de flores por especie en el conjunto de datos:
In [ ]: # Cargamos el dataset Iris en forma de DataFrame
# del repositorio de datos de Seaborn.
iris_df = sns.load_dataset('iris')

# Graficamos la distribución de las etiquetas.


sns.catplot(x='species', kind='count', data=iris_df, height=4, aspect=1.5)
plt.show()

Utilizando la función pairplot de Seaborn podemos visualizar todas las características


de Iris.
pairplot nos permite visualizar sobre la diagonal la distribución de las
clases respecto a cada característica en forma de histograma y por cada par
de características con un diagrama de dispersión. Puede tardar un poco en
generar la visualización.
In [ ]: sns.pairplot(iris_df, hue='species');

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 8/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Podemos observar que en general la clase setosa se encuentra bien separada de las clases
versicolor y virginica.
2.2 Conjunto de datos Boston
Cargamos el conjunto de datos Boston con scikit-learn:
In [ ]: # Loader del conjunto de datos Boston.
from sklearn.datasets import fetch_openml

boston = fetch_openml(name='boston', as_frame=True)

Revisamos las llaves del diccionario:


In [ ]: print(boston.keys())

dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_n


ames', 'DESCR', 'details', 'url'])

Boston es un conjunto de datos que plantea un problema de regresión y no existe una


distinción por clases de su variable objetivo.
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 9/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

En un problema de regresión la etiqueta asociada a cada ejemplo corresponde a una


cantidad. Por ejemplo, con el conjunto de datos Boston estamos buscando predecir el
precio de casas a partir de algunas de sus características.
In [ ]: # Características del dataset Boston.
print(boston['feature_names'])

['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRA
TIO', 'B', 'LSTAT']

Para saber mejor a que se refieren las abreviaciones podemos revisar la descripción del
conjunto de datos con el atributo DESCR :
In [ ]: print(boston['DESCR'])

**Author**:
**Source**: Unknown - Date unknown
**Please cite**:

The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic


prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980. N.B. Various transformations are used in the table on
pages 244-261 of the latter.
Variables in order:
CRIM per capita crime rate by town
ZN proportion of residential land zoned for lots over 25,000 sq.ft.
INDUS proportion of non-retail business acres per town
CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
NOX nitric oxides concentration (parts per 10 million)
RM average number of rooms per dwelling
AGE proportion of owner-occupied units built prior to 1940
DIS weighted distances to five Boston employment centres
RAD index of accessibility to radial highways
TAX full-value property-tax rate per $10,000
PTRATIO pupil-teacher ratio by town
B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
LSTAT % lower status of the population
MEDV Median value of owner-occupied homes in $1000's

Information about the dataset


CLASSTYPE: numeric
CLASSINDEX: last

Downloaded from openml.org.

Podemos ver que el conjunto de datos tiene características como índices de criminalidad,
impuestos, número de cuartos, entre otros, de propiedades en zonas urbanas. La variable
objetivo target corresponde a la variable MEDV , que representa la mediana del valor de
las casas en una zona.
Veamos los primeros ejemplos: 5

In [ ]: boston_df = pd.DataFrame(boston.data, columns=boston.feature_names)


boston_df.head(5)
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 10/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Out[ ]: CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LST
0 0.00632 18.0 2.31 0 0.538 6.575 65.2 4.0900 1 296.0 15.3 396.90 4
1 0.02731 0.0 7.07 0 0.469 6.421 78.9 4.9671 2 242.0 17.8 396.90 9
2 0.02729 0.0 7.07 0 0.469 7.185 61.1 4.9671 2 242.0 17.8 392.83 4
3 0.03237 0.0 2.18 0 0.458 6.998 45.8 6.0622 3 222.0 18.7 394.63 2
4 0.06905 0.0 2.18 0 0.458 7.147 54.2 6.0622 3 222.0 18.7 396.90 5

Nuestro objetivo es centrarnos en la evaluación del desempeño, por lo cual no realizaremos


más exploración de los datos. Procederemos directamente con entrenar modelos y evaluar
su desempeño.

3.(KNN)
Algoritmo: K-vecinos más cercanos
La clasificación basada en vecinos es un tipo de aprendizaje basado en ejemplos. El modelo
almacena los ejemplos vistos durante entrenamiento y clasifica un elemento no visto
usando una simple regla de votación por mayoría. Si se ubica un punto en el espacio de
características, se le asigna como clase el valor de la clase que tenga la mayor cantidad de
ejemplos en la vecindad del punto. Este ejemplo lo podemos ver ilustrado en la siguiente
imagen:

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 11/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Para ilustrar el concepto utilizaremos el conjunto de datos artificial de medias lunas moons
de Scikit-learn:
In [ ]: # Generamos un conjunto de datos artificial de dos clases en forma de medias lu
X_moons, y_moons = datasets.make_moons(n_samples=100, noise=0.3, random_state=0

Scikit-Learn provee una implementación del algoritmo KNN conocida como


KNeighborsClassifier . Esta tiene un parámetro n_neighbors (el valor ), un entero k

definido por el usuario que determina cuántos vecinos evalúa para determinar la clase de
una instancia nunca antes vista. La elección de este parámetro es definida totalmente por la
naturaleza de los datos.
Vamos a declarar el clasificador y entrenarlo con los datos del conjunto de datos artificial:
In [ ]: from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors= 1)
knn.fit(X_moons, y_moons)

Out[ ]: ▾ KNeighborsClassifier
KNeighborsClassifier(n_neighbors=1)

En este caso definimos que solo tenga en cuenta vecino más cercano. Si visualizamos la
k 1

región de decisión podremos observar unas franjas de color azul (clase A) en la zona
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 12/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

predominantemente naranja (clase B), en las cuales el valor más cercano es un único valor
de la clase A ubicado por aleatoriedad.
In [ ]: plot_decision_region(X_moons, y_moons, knn,
classes = ['A', 'B'],
title = 'KNN como clasificador de medias lunas')

Observaremos que dependiendo del valor de vecinos más cercanos que definamos, k

conseguimos diferentes funciones de ajuste, unas más suaves que otras.


Vamos a evaluar el efecto del parámetro en la complejidad del modelo.
k

4. Evaluación del desempeño - Clasificación


Binaria
Ahora que conocemos el conjunto de datos Iris, queremos entrenar un modelo que sea
capaz de clasificar de forma automática cualquier flor representada en un conjunto de datos
(a partir del ancho del pétalo (cm), largo del sépalo (cm)', largo del pétalo (cm)' y ancho del
pétalo (cm)).
En clasificación binaria se busca identificar la pertenencia de un ejemplo a una clase
específica, con un total de clases en la variable objetivo ( si pertenece y si no
2 1 0

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 13/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

pertenece). Para realizar un modelo de este tipo con Iris vamos a filtrar aquellas flores que
pertenezcan a las clases versicolor y virginica.
In [ ]: X = iris.data
y = iris.target

# Filtramos la clase 1 y 2 que corresponden a versicolor y virginica.


X = X[(y == 1) | (y == 2)]
y = y[(y == 1) | (y == 2)]

# Para que las clases queden entre 0 y 1


# le restamos 1 a todos los valores.
y = y - 1

In [ ]: X.shape, y.shape

((100, 4), (100,))


Out[ ]:

Inicialmente, entrenamos el modelo KNeighborsClassifier con todos los datos y


verificamos los valores predichos.
In [ ]: # Módulo de Scikit-learn para modelos lineales.
from sklearn.neighbors import KNeighborsClassifier

# Declaramos y entrenamos el modelo.


classifier = KNeighborsClassifier()
classifier.fit(X, y)
predictions = classifier.predict(X)

n = 30
print(f'Número de instancias a predecir: {n}')
print(f'Valores reales: {y[:n]}')
print(f'Valores predichos: {predictions[:n]}')

Número de instancias a predecir: 30


Valores reales: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Valores predichos: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
0]

Pregunta: ¿Cómo evaluamos el desempeño de nuestro clasificador?

4.1. Error de entrenamiento y generalización


Un modelo de aprendizaje de máquina tiene como objetivo principal hacer predicciones de
manera acertada sobre ejemplos nunca antes vistos por el modelo. Esto se conoce como
error de generalización. Para poder medir el error de generalización, dividimos el conjunto
de datos en dos particiones:
Entrenamiento: Se usa para entrenar el modelo.
Prueba: Se usa para estimar el error de generalización.
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 14/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

En la siguiente imagen encontramos una ilustración de cómo se hace un particionamiento


en entrenamiento y prueba de manera gráfica:

Train Test

X y
1.52 4.5 1.3 0

-0.5 4 3.9 1

2.5 1.0 3.0 1

3.1 2.0 5.0 1

-0.1 2.0 4.5 0

Una de las prácticas recomendadas, es particionar los datos para entrenamiento y 70%

30% para prueba. Cuando el número de muestras es muy grande ( ), podemos ≥ 70K

reducir el porcentaje de muestras para prueba, a . Sin embargo, deben hacerse


90 − 10%

unas aclaraciones sobre la generalización:


El conjunto de prueba debe ser una muestra representativa del conjunto de datos.
El muestreo de ejemplos debe hacerse de forma independiente e idénticamente
aleatoria de una distribución. Esto quiere decir, que el muestreo de un ejemplo no está
influenciado por el muestreo de otro.
La distribución es estacionaria. Es decir, no cambia a lo largo del conjunto de datos.
Los ejemplos son muestreados desde particiones de la misma distribución. Es
decir, no se deben crear nuevas características en la partición de prueba.
Adicionalmente, debemos tener en cuenta que se conserve la distribución de las etiquetas
de los datos tanto en entrenamiento como en prueba (estratificación). En un notebook
posterior se va a estudiar en más detalle los efectos de hacer una partición estratificada.
Como se discutió en materiales previos, Scikit-Learn permite particionar un conjunto de
datos en entrenamiento y prueba de manera automática con una diversidad de funciones de
selección de modelos.
A continuación, vamos a dividir el conjunto en para entrenamiento y para prueba:
70% 30%

In [ ]: from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X,


y,
test_size=0.3,
stratify=y,
random_state=42)

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 15/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

El argumento stratify le indica a Scikit-learn que se desea estratificar los datos con
respecto a y .
Vamos a verificar el número de muestras de ambas particiones y la distribución de clases
de cada una.
In [ ]: print(f'Número de muestras en entrenamiento: {X_train.shape[0]}')
print(f'Número de muestras en prueba: {X_test.shape[0]}')
print(f'Número de características: {X_train.shape[1]}')

# La función np.bincount permite realizar el conteo


# de ocurrencia de valores enteros en un arreglo.

print(f'Distribución de clases en entrenamiento: {np.bincount(y_train)}')


print(f'Distribución de clases en prueba: {np.bincount(y_test)}')

Número de muestras en entrenamiento: 70


Número de muestras en prueba: 30
Número de características: 4
Distribución de clases en entrenamiento: [35 35]
Distribución de clases en prueba: [15 15]

Procedemos a entrenar un modelo de clasificación por K-vecinos sobre la partición de


entrenamiento:
Nota: La partición de prueba está destinada a validar el desempeño del
modelo y sus datos no deberían ser vistos por el clasificador.
In [ ]: from sklearn.neighbors import KNeighborsClassifier

classifier = KNeighborsClassifier(n_neighbors=5)
classifier.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsClassifier

KNeighborsClassifier()

4.2. Exactitud o accuracy


Con el objetivo de conocer el desempeño de nuestro clasificador se suele medir
cuantitativamente cuantas predicciones fueron correctas. Este número se conoce como
exactitud o accuracy en inglés y es la métrica por defecto en muchos de los modelos de
clasificación de Scikit-learn.
In [ ]: prediction = classifier.predict(X_test)

# Número de valores acertados de una predicción.


np.mean(prediction == y_test)

0.8666666666666667
Out[ ]:

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 16/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Scikit-learn nos permite evaluar también el accuracy con la función .score de un


clasificador:
In [ ]: classifier.score(X_test, y_test)

0.8666666666666667
Out[ ]:

También se puede realizar de forma explícita mediante la función accuracy_score del


módulo sklearn.metrics .
In [ ]: from sklearn.metrics import accuracy_score

y_pred = classifier.predict(X_test)
accuracy_score(y_test, y_pred)

0.8666666666666667
Out[ ]:

4.3. Matriz de confusión


No obstante, es importante conocer qué clases clasifica mejor. Para poder visualizar esta
información, usaremos la matriz de confusión, la cual es una clase especial de tabla de
contingencia en la cual se comparan las clases reales contra las clases predichas por el
clasificador.
Scikit-Learn nos permite construir la matriz de confusión usando la función
sklearn.metrics.confusion_matrix , que recibe como argumento dos listas o
arreglos de NumPy:
y: etiquetas reales del conjunto de datos.
y
^: etiquetas predichas por el clasificador sobre el conjunto de datos.
In [ ]: from sklearn.metrics import confusion_matrix

y_pred = classifier.predict(X_test)
cnf_matrix = confusion_matrix(y_test, y_pred)

La función confusion_matrix regresa una matriz de tamaño [ , ], n_clases n_clases

dónde corresponde al número de clases únicas en el conjunto de datos. La matriz


n_clases

de confusión nos permite comparar el rendimiento de nuestro clasificador clase por clase.
In [ ]: cnf_matrix

array([[12, 3],
Out[ ]:
[ 1, 14]])

A continuación, vamos a usar la función definida al comienzo de este notebook para generar
de una forma más visual la matriz de confusión.

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 17/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

In [ ]: class_names = iris.target_names[1:]
list_confusion_matrix(cnf_matrix,class_names)

Out[ ]: Valor predicho


versicolor virginica
Valor real versicolor 12 3
virginica 1 14

¿Cómo interpretarla?
Los valores en la diagonal indican los aciertos de nuestro clasificador. Por ejemplo,
sabemos que de ejemplos de la clase virginica, supo clasificar , mientras que de
15 14

15 ejemplos de la clase versicolor se equivocó en . 3

¿Qué pasa cuando el problema es desbalanceado?


Supongamos un clasificador con el siguiente desempeño sobre un conjunto de datos:
G

99
Accuracy = = 99%
100
1
Error = = 1%
100

¿Es un buen clasificador?


Para medir efectivamente si es un buen clasificador, presentamos la matriz de confusión
G

producto de sus predicciones.


In [ ]: # Clases de ejemplo.
class_names = ['C_+', 'C_-']

# Valores reales y predichos de ejemplo.


y_pred_G = np.ones(100)
y_test_G = np.ones(100)
y_test_G[-1] = 0

mat = confusion_matrix(y_test_G, y_pred_G)


list_confusion_matrix(mat, class_names)

Out[ ]: Valor predicho


C_+ C_-
Valor real C_+ 0 1
C_- 0 99

A pesar de que el modelo clasificó correctamente muestras de la clase negativa, falló en


99

clasificar la única muestra positiva del conjunto de datos.

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 18/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

4.4. Métricas de precisión, Recall y F1


La matriz de confusión nos permite calcular otra serie de medidas para evaluar el
desempeño del clasificador. Para introducir estas medidas, vamos a descomponer la matriz
de confusión en cuatro partes:

Los componentes de esta matriz pueden interpretarse como:


Verdaderos positivos (VP). Resultado correcto para la clase positiva.
Verdaderos negativos (VN). Ausencia correcta para la clase positiva.
Falsos positivos (FP). Resultados inesperados.
Falsos negativos (FN). Resultados faltantes.
Nota: Vale la pena aclarar que, en clasificación binaria, los términos positivo o
negativo se refieren a la predicción del clasificador (clase), mientras que
verdadero o falso se refieren a si la predicción fue correcta o no.
De esta matriz podemos volver a escribir las definiciones de accuracy y error:
VP+VN
accuracy =
VP+VN+FP+FN

FP+FN
error =
VP+VN+FP+FN

Además de estas, podemos definir las siguientes métricas:


Precisión: se puede definir como la habilidad del clasificador de no clasificar una
muestra como positiva cuando es negativa.
VP
PRE =
VP+FP

Recall (índice de recuperación): se puede definir como la capacidad del clasificador


de encontrar todas las muestras positivas.
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 19/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
TP
REC =

score: se define como el promedio ponderado entre la precisión y el recall.


FN+TP

F1

PRE∗REC
F1 = 2 ∗
PRE+REC

Evaluemos la precisión y el índice de recuperación de nuestro clasificador : G

Precision = = No definida
0

Recall
0
0
Recall = = 0%
1

Scikit-learn provee diferentes funciones para calcular estas tres medidas. Estas son:
Precisión: sklearn.metrics.precision_score
Recall: sklearn.metrics.recall_score
F1 score: sklearn.metrics.f1_score
Vamos a medir el desempeño sobre el clasificador : G

In [ ]: from sklearn import metrics

print(f'Precisión: {metrics.precision_score(y_test_G, y_pred_G, pos_label=0)}')


print(f'Recall: {metrics.recall_score(y_test_G, y_pred_G, pos_label=0)}')
print(f'F_1 score: {metrics.f1_score(y_test_G, y_pred_G, pos_label=0)}')

Precisión: 0.0
Recall: 0.0
F_1 score: 0.0

El argumento pos_label indica cual etiqueta corresponde a la clase


positiva. Para nuestro ejemplo hemos tomado la clase como la clase 0

positiva. Por otro lado, Scikit-learn le asigna el valor a estas métricas,0

aunque no estén bien definidas (por ejemplo si el denominador es 0).


Regresando al problema de clasificación sobre Iris, podemos calcular el valor de la
precisión, recall y de la misma manera:
F1

In [ ]: print(f'Precisión: {metrics.precision_score(y_test, prediction):.4f}')


print(f'Recall: {metrics.recall_score(y_test, prediction):.4f}')
print(f'F_1 score: {metrics.f1_score(y_test, prediction):.4f}')

Precisión: 0.8235
Recall: 0.9333
F_1 score: 0.8750

5.Multiclase
Evaluación del desempeño - Clasificación

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 20/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Esta vez usaremos todos los ejemplos de cada una de las tres especies de flor del dataset
Iris. Para obtener una representación gráfica, nos limitaremos a dos variables numéricas de
entrada (longitud del sépalo en y longitud del pétalo en ).
x y

Primero que todo realizamos la partición de los datos en entrenamiento y prueba:


In [ ]: # Esta vez usamos las 3 clases y solo 2 variables.

X = iris.data[:,[0, 2]]
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,


stratify=y, random_state=31

In [ ]: print('El shape de X_train es:', X_train.shape)


print('El shape de y_train es:', y_train.shape)

print('El shape de X_test es:', X_test.shape)


print('El shape de y_test es:', y_test.shape)

El shape de X_train es: (105, 2)


El shape de y_train es: (105,)
El shape de X_test es: (45, 2)
El shape de y_test es: (45,)

Entrenaremos dos modelos KNN clf_k1 y clf_k1 , donde clf_k5 usará los vecinos
5

más cercanos y clf_k1 solo el más cercano.


Definimos el clasificador clf_k1 y lo entrenamos:
In [ ]: # Clasificador KNN con k = 1.
clf_k1 = KNeighborsClassifier(n_neighbors=1)
clf_k1.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsClassifier
KNeighborsClassifier(n_neighbors=1)

Definimos el clasificador clf_k5 y lo entrenamos:


In [ ]: # Clasificador KNN con k = 5.
clf_k5 = KNeighborsClassifier(n_neighbors=5)
clf_k5.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsClassifier

KNeighborsClassifier()

A continuación, visualizamos las regiones de decisión para cada clasificador:


In [ ]: plot_decision_region(X_test, y_test, clf_k1, iris.target_names,
title = 'Región de Decisión KNN (k = 1)')

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 21/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

In [ ]: plot_decision_region(X_test, y_test, clf_k5, iris.target_names,


title = 'Región de Decisión KNN (k = 5)')

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 22/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Podemos notar que a medida que n_neighbors es mayor, la región de decisión es más
suave. En el caso de n_neighbors = 1 , el modelo se está ajustando a las
particularidades de la partición de entrenamiento.
5.1. Matriz de confusión con más de 2 clases
La matriz de confusión se puede extender al problema multiclase de la siguiente manera:
Primero, visualizaremos la matriz de confusión para el modelo KNN con n_neighbors=1 :
In [ ]: y_pred = clf_k1.predict(X_test)

# Generamos y mostramos la matriz de confusión.


mat = confusion_matrix(y_test, y_pred)
list_confusion_matrix(mat, iris.target_names)

Out[ ]: Valor predicho


setosa versicolor virginica
Valor real setosa 15 0 0
versicolor 0 14 1
virginica 0 3 12

Ahora calculamos la matriz de confusión para el modelo con n_neighbors=5 :


In [ ]: y_pred = clf_k5.predict(X_test)

# Generamos y mostramos la matriz de confusión.


mat = confusion_matrix(y_test, y_pred)
list_confusion_matrix(mat, iris.target_names)

Out[ ]: Valor predicho


setosa versicolor virginica
Valor real setosa 15 0 0
versicolor 0 15 0
virginica 0 2 13

A simple vista podemos identificar que:


La clase setosa es fácil de clasificar para ambos clasificadores.
La clase virginica es más difícil de clasificar que la clase versicolor, pues con
n_neighbors=5 el modelo obtiene puntaje perfecto para versicolor pero sigue
fallando con ejemplos de virginica.
2

De nuevo, es útil usar una medida de desempeño para comparar cuantitativamente el


rendimiento de ambos modelos.
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 23/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

5.2. Accuracy multiclase


El accuracy multiclase se define como la fracción de predicciones correctas del clasificador.
Se puede calcular con la siguiente fórmula:
n−1
1
acc(y, y
^) = ∑ 1(y
^ = yi )
i
n
i=0

dónde corresponde a la lista de etiquetas de verdad de nuestro conjunto de datos,


y

mientras corresponde a los valores predichos por nuestro clasificador para el mismo
y
^

conjunto de datos en el mismo orden, y corresponde al número de ejemplos del


n

conjunto.
Scikit-Learn nos permite calcular el accuracy de la misma manera que con un modelo de
clasificación binaria:
In [ ]: print(f'Accuracy n_neighbors = 1: {clf_k1.score(X_test, y_test):.4f}')
print(f'Accuracy n_neighbors = 5: {clf_k5.score(X_test, y_test):.4f}')

Accuracy n_neighbors = 1: 0.9111


Accuracy n_neighbors = 5: 0.9556

El modelo de regresión logística se desempeña mejor frente al modelo "Uno vs Todos". El


error se puede definir como la fracción de predicciones incorrectas del clasificador:
In [ ]: print(f'Error n_neighbors = 1: {1 - clf_k1.score(X_test, y_test):.4f}')
print(f'Error n_neighbors = 5: {1 - clf_k5.score(X_test, y_test):.4f}')

Error n_neighbors = 1: 0.0889


Error n_neighbors = 5: 0.0444

5.3. Precisión, recall y score en clasificación multiclase


F1

La métrica de precisión era calculada con base a la matriz de confusión del problema de
clasificación binaria. De igual manera, se puede extender como medida de desempeño para
el problema multiclase de varias formas. Recordemos que:
VP
Precisión =
VP+FP

VP
Recall =
VP+FN

PRE∗REC
F1 = 2 ∗
PRE+REC

Para ilustrar como se calcula cada una de estas medidas, usaremos el clasificador con
n_neighbors=1 .

Primero calculamos la precisión para cada clase y luego determinamos la forma en la que
combinamos las precisiones de cada clase. Scikit-Learn nos permite calcular la precisión y
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 24/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

recall por clase así:


Nota: Al definir el argumento average = None se le indica al cálculo de la métrica
que se retorne el valor de la métrica por cada clase, en vez de realizar una agregación
general para todo el conjunto de datos.
In [ ]: from sklearn.metrics import precision_score, recall_score
y_pred = clf_k1.predict(X_test)

print(f'Orden de las etiquetas: {iris.target_names}')


print(f'Precisión por clase: \t{precision_score(y_test, y_pred, average=None)}'
print(f'Recall por clase: \t{recall_score(y_test, y_pred, average=None)}')

Orden de las etiquetas: ['setosa' 'versicolor' 'virginica']


Precisión por clase: [1. 0.82352941 0.92307692]
Recall por clase: [1. 0.93333333 0.8 ]

Añadimos un par de cálculos a nuestra tabla con la matriz de confusión, donde reflejamos la
suma de los valores totales de verdaderos positivos, falsos positivos y falsos negativos.
Existen varias formas de combinar o agregar las medidas de precisión y recall por clase, que
son definidas a partir del argumento average de las métricas mencionadas. Algunos de
los posibles valores son:
micro : Cuenta el total de positivos verdaderos, falsos positivos y falsos negativos
para realizar el cálculo de la métrica.
macro : Calcula la métrica por cada clase y luego la promedia (sin tener en cuenta el
balance de clases).
weighted : Calcula la precisión por clase y luego la promedia teniendo en cuenta el
balance de clases.
Clase VP FP FN PRE REC
Setosa 15 0 0 1.0 1.0
Versicolor 14 3 1 0.824 0.933
Virginica 12 1 3 0.923 0.800
Sum(micro) 41 4 4 0.911 0.911
Avg(macro) 0.916 0.911
In [ ]: print(f"Precisión macro: {precision_score(y_test, y_pred, average='macro'):.4f}
print(f"Precisión micro: {precision_score(y_test, y_pred, average='micro'):.4f}
print(f"Precisión ponderada: {precision_score(y_test, y_pred, average='weighted

print(f"Recall macro: {recall_score(y_test, y_pred, average='macro'):.4f}")


print(f"Recall micro: {recall_score(y_test, y_pred, average='micro'):.4f}")
print(f"Recall ponderada: {recall_score(y_test, y_pred, average='weighted'):.4f

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 25/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión
Precisión macro: 0.9155
Precisión micro: 0.9111
Precisión ponderada: 0.9155

Recall macro: 0.9111


Recall micro: 0.9111
Recall ponderada: 0.9111

Como observamos, estos valores corresponden a las dos últimas filas que se calcularon
sobre la tabla de precisión y recall. Vale la pena anotar que weighted y macro son
iguales cuando la clase es balanceada, tal como en el ejemplo de Iris. Esto se puede
extender al cálculo del . Recordemos que el
F1 score es un promedio ponderado de
F1 score

la precisión y el recall.
In [ ]: from sklearn.metrics import f1_score

print(f"F1 macro: {f1_score(y_test, y_pred, average='macro'):.4f}")


print(f"F1 micro: {f1_score(y_test, y_pred, average='micro'):.4f}")
print(f"F1 ponderada: {f1_score(y_test, y_pred, average='weighted'):.4f}")

F1 macro: 0.9107
F1 micro: 0.9111
F1 ponderada: 0.9107

Para finalizar, Scikit-Learn permite realizar un reporte general con todas las métricas
mencionadas con la función classification_report del módulo sklearn.metrics :
In [ ]: from sklearn.metrics import classification_report

print(classification_report(y_test,
y_pred,
target_names=iris.target_names,
digits=4))

precision recall f1-score support

setosa 1.0000 1.0000 1.0000 15


versicolor 0.8235 0.9333 0.8750 15
virginica 0.9231 0.8000 0.8571 15

accuracy 0.9111 45
macro avg 0.9155 0.9111 0.9107 45
weighted avg 0.9155 0.9111 0.9107 45

Utilizamos el parámetro digits para indicar cuantos dígitos reportar y target_names


para indicarle a classification_report los nombres de las clases.
Pregunta: ¿Por qué classification_report no reporta el promedio
micro ?

6. Evaluación del desempeño - Regresión


file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 26/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

En esta sección veremos la evaluación del desempeño para modelos de regresión, usando
el conjunto de datos Boston. Como es usual, primero realizamos una partición de
entrenamiento y prueba tomando el para prueba. 30%

Nota: Tenga en cuenta que no realizamos estratificación debido a que se trata


de un problema de regresión.
Importaremos y particionaremos los datos del conjunto de datos de Boston:
In [ ]: from sklearn.datasets import fetch_openml

boston = fetch_openml(name='boston', as_frame=True)

In [ ]: X, y = boston.data, boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y,
random_state=12, test_size=

6.1. KNN como regresor


El algoritmo KNN también es aplicable en problemas de regresión. La clase
KNeighborsRegressor de Scikit-learn aplica una interpolación local de la etiqueta de los
n vecinos más cercanos para realizar la predicción.
Por defecto, KNeighborsRegressor toma el promedio de la etiqueta de los vecinos. Sin
embargo, también se puede tomar un promedio ponderado por el inverso de la distancia
especificando el parámetro weights = 'distance' .
Entrenaremos un modelo KNN con vecinos para regresión:
5

In [ ]: from sklearn.neighbors import KNeighborsRegressor

model = KNeighborsRegressor(n_neighbors=5,
weights='distance')

model.fit(X_train, y_train)

Out[ ]: ▾ KNeighborsRegressor
KNeighborsRegressor(weights='distance')

Si usamos el método score del modelo nos retornará la métrica conocida como el
coeficiente de determinación, y no la exactitud del modelo. En un problema de regresión
no tiene sentido hablar de exactitud u otras métricas cómo la precisión o el recall.
In [ ]: model.score(X_test, y_test)

0.4743034798512158
Out[ ]:

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 27/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Antes de explorar el concepto del coeficiente de determinación primero introduciremos las


métricas de error cuadrático medio (MSE) y de la raíz del error cuadrático medio
(RMSE).
6.2. Error cuadrático medio (MSE, Mean-Squared-Error)
El error cuadrático medio se define como:
n
1
2
MSE = ^ − yi )
∑( y i
n
i=0

Donde:
y − Etiqueta Real

^ − Predicción
y

El MSE toma la diferencia entre la etiqueta y la predicción (error) por cada ejemplo, las eleva
al cuadrado y las suma y las divide sobre el total de ejemplos. En otras palabras, el MSE es
la media del error al cuadrado . Note que por definición el MSE solo toma valores
(y
^i − y i )
2

mayores o iguales a . 0

Tomar el cuadrado de los errores permite evaluar diferencias positivas tanto negativas de la
misma manera. Tenga en cuenta que esto también implica que el orden de y no es ^
y yi

relevante para el cálculo del error.


i

Tomar el cuadrado también penaliza los errores más grandes. Un MSE pequeño índica que,
en promedio, los errores para todos los ejemplos son pequeños. Otra manera de interpretar
el MSE es como la varianza de los errores (que tan dispersos son). El MSE tiene muchas
interpretaciones estadísticas importantes, como la descomposición en varianza y sesgo, las
cuales no están dentro del alcance de este curso.
En Scikit-learn, el MSE puede ser calculado con el método mean_squared_error de la
siguiente manera:
In [ ]: from sklearn.metrics import mean_squared_error

y_pred = model.predict(X_test)

mean_squared_error(y_test, y_pred)

46.11812861720047
Out[ ]:

6.3. Raíz del error cuadrático medio (RMSE, Root-Mean-


Squared-Error)
La raíz del error cuadrático medio se define como:
file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 28/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

 n
 1
2
RMSE =  ∑( y
^ − yi )
i
⎷ n
i=0

Donde:
y − Etiqueta Real

y
^ − Predicción

Aunque sea simplemente la raíz del MSE, el RMSE tiene la ventaja de ser interpretable en las
unidades originales del problema.
EL RMSE puede ser calculado de la siguiente manera:
In [ ]: np.sqrt(mean_squared_error(y_test, y_pred))

6.791032956568571
Out[ ]:

6.4. Coeficiente de Determinación ( ) R


2

El coeficiente de determinación (o cuadrado) se define como:


R

1 n
2
∑ ^ − yi )
(y
n i=0 i
2
R = 1 −
n
1 2
n
∑ (yi − ȳ )
i=0

Donde:
y − Etiqueta Real

y
^ − Predicción

1 n
2
ȳ − Media de y: ∑ y
n i=0 i

Podemos notar que el numerador de la división corresponde al error cuadrático medio


(varianza de los errores) y que el denominador de la división corresponde a la varianza de
las etiquetas.
La varianza de las etiquetas está escrita de una manera muy similar al MSE, de hecho, si
definiéramos un modelo que para un conjunto de ejemplos prediga para cada uno de los
ejemplos la media de sus etiquetas (llamémoslo modelo de la media) y calculáramos su MSE
sería exactamente esta varianza.
El modelo de la media es lo que se considera como un baseline. Los baseline o modelos de
línea base son modelos sencillos usados para comparar su desempeño contra modelos más
complejos.

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 29/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Dicho lo anterior, si definimos el MSE del modelo de la media como MMSE (Mean Model
Squared Error) entonces puede ser escrito de la siguiente manera:
R
2

2
MSE MMSE − MSE
R = 1 − =
MMSE MMSE

De esta manera, podemos interpretar el coeficiente de determinación como una proporción


de mejora en la predicción de un modelo comparado con el modelo de la media de las
etiquetas. El mejor valor de es y también puede tomar valores negativos (para modelos
R
2
1

mucho peores). También cabe notar que el coeficiente de determinación es dependiente del
conjunto de datos, y por lo tanto no es comparable entre distintos conjuntos de datos.
Se dice que el coeficiente representa la proporción de la varianza (de y) que ha sido
explicada por las variables independientes en el modelo. Indica que tan bien serán
predichos ejemplos nunca antes vistos por el modelo, a través de la proporción de esa
varianza explicada.
Como ya se mostró, el coeficiente de determinación puede ser calculado a través de la
función score de los modelos de regresión. Sin embargo, también se puede calcular a
través del método r2_score de sklearn.metrics :
In [ ]: from sklearn.metrics import r2_score

y_pred = model.predict(X_test)

print(f'Coeficiente de Determinación usando score:, {model.score(X_test, y_test


print(f'Coeficiente de Determinación con r2_score:, {r2_score(y_test, y_pred):.

Coeficiente de Determinación usando score:, 0.4743


Coeficiente de Determinación con r2_score:, 0.4743

De esta manera podemos determinar que el modelo representa una mejora sobre el modelo
de las medias. Sin embargo, aún puede mejorarse el desempeño.

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn para la
evaluación del desempeño de sus modelos, además de material de apoyo teórico para
reforzar estos conceptos:
scikit-learn - Model Evaluation
mlcourse.ai - Decision Trees and KNN
Metrics to understand regression model in plain english
Difference Between Classification and Regression in Machine Learning

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 30/31
7/5/23, 10:12 M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión

Créditos
Profesor: Fabio Augusto González
Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería

file:///Users/mac/Downloads/M2U2_Evaluación_del_Desempeño_de_Clasificación_y_Regresión.html 31/31
7/5/23, 16:00 grupo4

In [ ]: import numpy as np # algebra lineal


import matplotlib.pyplot as plt # visualizacion
from sklearn.svm import SVR # modelamiento
from sklearn.preprocessing import MinMaxScaler # transformar datos
from sklearn.model_selection import train_test_split # particionar los datos (v
from sklearn.metrics import mean_squared_error

plt.style.use("ggplot")

Carga de Datos
In [ ]: time = np.random.uniform(0, 10, size=(1000, 1))
position = np.exp(-0.2 * time) * np.cos(2 * time) + np.random.normal(0, 0.1, si

In [ ]: fig, ax = plt.subplots()
ax.scatter(time, position, alpha=0.2)
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

Preprocesamiento
In [ ]: np.exp(1000)

<ipython-input-11-47a6eab891c2>:1: RuntimeWarning: overflow encountered in exp


np.exp(1000)

file:///Users/mac/Downloads/grupo4.html 1/4
7/5/23, 16:00 grupo4
inf
Out[ ]:

In [ ]: np.exp(-0.1)

0.9048374180359595
Out[ ]:

x − min (x)

x =
max (x) − min (x)

In [ ]: scaler = MinMaxScaler().fit(time)
time_t = scaler.transform(time)

In [ ]: fig, ax = plt.subplots()
ax.scatter(time_t, position, alpha=0.2)
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

In [ ]: time_n = scaler.inverse_transform(time_t)
time_n.min(), time_n.max()

(0.003010875474882546, 9.98506355700189)
Out[ ]:

Validacion Cruzada
In [ ]: time_train, time_test, position_train, position_test = train_test_split(
time_t, position, test_size=0.3, random_state=0
file:///Users/mac/Downloads/grupo4.html 2/4
7/5/23, 16:00 grupo4
)

Modelamiento
In [ ]: model = SVR(gamma=100).fit(time_train, position_train)

/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py:1143: DataC
onversionWarning: A column-vector y was passed when a 1d array was expected. P
lease change the shape of y to (n_samples, ), for example using ravel().
y = column_or_1d(y, warn=True)

Evaluacion
In [ ]: model.score(time_train, position_train)

0.9324830029391247
Out[ ]:

In [ ]: model.score(time_test, position_test)

0.9302732695254807
Out[ ]:

In [ ]: x_range = np.linspace(0, 1, 1000).reshape(-1, 1)


y_pred = model.predict(x_range)

In [ ]: fig, ax = plt.subplots()
ax.scatter(time_t, position, alpha=0.2)
ax.plot(x_range, y_pred, color="k")
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

file:///Users/mac/Downloads/grupo4.html 3/4
7/5/23, 16:00 grupo4

In [ ]: position_train.mean()

0.008677297702295469
Out[ ]:

In [ ]: y_pred = model.predict(time_test)
np.sqrt(mean_squared_error(position_test, y_pred))

0.09755202307869525
Out[ ]:

In [ ]: !jupyter nbconvert --to html /content

file:///Users/mac/Downloads/grupo4.html 4/4
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Métodos de clasificación no lineal


En este notebook se discutirán los métodos de clasificación no lineal, y en particular, se
presentará la implementación de modelos basados en árboles de decisión y de máquinas de
vectores de soporte (SVM). El material se desarrollará con la ayuda de la librería de
aprendizaje automático Scikit-Learn y de otras librerías comunes como NumPy, Pandas y
Matplotlib.
Finalmente, se discutirá la necesidad de identificar lo hiperparámetros más adecuados para
un modelo de aprendizaje automático, y se discutirán técnicas para estimarlo de forma
robusta, como la validación cruzada de pliegues y grid search.
k

1. Dependencias
Importamos las librerías necesarias y definimos algunas funciones básicas de visualización
que vamos a usar en algunos ejemplos.
1.1. Dependencias
Para la construcción de modelos y ejecución de procedimientos metodológicos de
aprendizaje automático, utilizaremos la librería Scikit-learn ( sklearn ) y varias de sus
funciones y conjuntos de datos.
In [ ]: # Actualizamos scikit-learn a la última versión
!pip install -U scikit-learn

# Importamos scikit-learn
import sklearn

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 1/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-p
ackages (1.2.2)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.1.1)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-p
ackages (from scikit-learn) (1.10.1)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn) (3.1.0)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.22.4)

Importamos además algunas librerías básicas y configuraciones de Python.


In [ ]: #Librerías básicas NumPy y Pandas.
import numpy as np
import pandas as pd

#Matplotlib y Seaborn - Librerías de visualización.


import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

# Visualización de grafos con graphviz.


import graphviz

In [ ]: # Configuraciones para las librerías y módulos usados.

# Ignoramos las advertencias o warnings.


import warnings
warnings.simplefilter(action='ignore')

# Configuramos el formato por defecto de la


# librería de visualización Matplotlib.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
mpl.rcParams['figure.dpi'] = 105
mpl.rcParams['figure.figsize'] = (9, 7)

Este material se realizó con las siguientes versiones:


Python: 3.7.10
Scikit-learn: 0.24.1
NumPy: 1.19.5
Pandas: 1.1.5
Matplotlib: 3.2.2
Seaborn: 0.11.1
Graphviz: 0.10.1
In [ ]: # Versión de Python y las demás librerías.
!python --version
print('Scikit-learn', sklearn.__version__)
print('NumPy', np.__version__)
print('Pandas', pd.__version__)
print('Matplotlib', mpl.__version__)
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 2/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
print('Seaborn', sns.__version__)
print('Graphviz', graphviz.__version__)

Python 3.9.16
Scikit-learn 1.2.2
NumPy 1.22.4
Pandas 1.4.4
Matplotlib 3.7.1
Seaborn 0.12.2
Graphviz 0.20.1

1.2. Funciones de utilidad y visualización


Para ilustrar los ejemplos discutidos en este material utilizaremos algunas funciones que
permiten visualizar de manera general los datos, junto a las funciones de predicción
obtenidas con cada modelo.
Nota: Matplotlib y Seaborn se encuentran por fuera del alcance de este
módulo. No es necesario que entienda estas funciones en detalle para sacar
partido del resto del contenido puesto a su disposición. Usted decide si leer o
no estas funciones en profundidad. Si decide omitir esta sección, continúe
directamente con la siguiente sección, en donde se discutirán los conjuntos
de datos que vamos a utilizar.
In [ ]: # Función para visualizar un conjunto de datos de dos variables en un plano 2D.
def plot_data(X, y, model = None, ax = None, title=None):

if ax is None:
_, ax = plt.subplots(dpi = 110)

if model is not None:


pred_fun = gen_pred_fun(model)
plot_decision_region(X, pred_fun, ax)

y_unique = np.unique(y)
df = pd.DataFrame({'x1': X[:,0], 'x2': X[:,1], 'Clases': y})
sns.set_theme()
sns.scatterplot(data = df, x = 'x1', y = 'x2',
hue = 'Clases',style = 'Clases', ax = ax, palette = 'Set1')

In [ ]: # Función para visualizar la superficie de decisión de un clasificador.


def plot_decision_region(X, pred_fun, ax=None):
min_x, max_x = np.min(X[:, 0]), np.max(X[:, 0])
min_y, max_y = np.min(X[:, 1]), np.max(X[:, 1])

min_x = min_x - (max_x - min_x) * 0.05


max_x = max_x + (max_x - min_x) * 0.05
min_y = min_y - (max_y - min_y) * 0.05
max_y = max_y + (max_y - min_y) * 0.05

x_vals = np.linspace(min_x, max_x, 100)


y_vals = np.linspace(min_y, max_y, 100)

XX, YY = np.meshgrid(x_vals, y_vals)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 3/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
grid_r, grid_c = XX.shape

ZZ = np.zeros((grid_r, grid_c))

for i in range(grid_r):
for j in range(grid_c):
ZZ[i, j] = pred_fun(XX[i, j], YY[i, j])

cs = ax.contourf(XX, YY, ZZ, 100, cmap = plt.cm.Pastel1, vmin = 0, vmax = n


ax.get_figure().colorbar(cs, ax=ax, )
ax.set_xlabel("x")
ax.set_ylabel("y")

In [ ]: # Función para visualizar la curva de aprendizaje a partir


# del error de entrenamiento y de generalización.
def plot_learning_curve(train_error, generalization_error):
n = len(train_error)
if len(train_error) != len(generalization_error):
print("Las secuencias de error de entrenamiento y generalización deben tene
return

balance_point = np.array(generalization_error).argmin() + 1
plt.figure(figsize = (8, 5), dpi = 105)

plt.plot(range(1, n + 1), train_error, label="Entrenamiento")


plt.plot(range(1, n + 1), generalization_error, label="Generalización")
plt.xticks(range(0, n + 1, 2))
plt.xlabel("Profundidad máxima")
plt.ylabel("Error")
y_min, y_max = plt.gca().get_ylim()
plt.vlines(balance_point, y_min, y_max, colors = ['red'], linestyles = ['dash
plt.ylim([y_min, y_max])
plt.text(balance_point + 1, 0.165, 'Punto de balance')
plt.legend();

In [ ]: #Función para generar la función de predicción de un clasificador entrenado pre


def gen_pred_fun(clf):
def pred_fun(x1, x2):
x = np.array([[x1, x2]])
return clf.predict(x)[0]
return pred_fun

2. Conjuntos de datos
Para los ejemplos desarrollados en el transcurso de material, se usarán datos de Scikit-
Learn de carácter real (usando Loaders) y sintético (usando Generators).
2.1. Conjunto de datos Iris
En este material retomaremos el conjunto de datos Iris para ilustrar algunos ejemplos. Para
esto, usaremos la función load_iris de Scikit-Learn.

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 4/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: # Loader del dataset iris.


from sklearn.datasets import load_iris

iris = load_iris()

X = iris.data
y = iris.target

In [ ]: # Información general del conjunto.

print(f'X ~ {X.shape[0]} muestras x {X.shape[1]} características.')


print(f'y ~ {y.shape[0]} muestras.')
print('\nPrimeras 5 muestras:\n', X[:5, :])
print('\nPrimeras 5 etiquetas:\n', y[:5])

X ~ 150 muestras x 4 características.


y ~ 150 muestras.

Primeras 5 muestras:
[[5.1 3.5 1.4 0.2]
[4.9 3. 1.4 0.2]
[4.7 3.2 1.3 0.2]
[4.6 3.1 1.5 0.2]
[5. 3.6 1.4 0.2]]

Primeras 5 etiquetas:
[0 0 0 0 0]

In [ ]: # Graficamos en un área 2d.


plot_data(X, y)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 5/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

2.2. Conjuntos de datos sintéticos


Además de Iris, vamos a trabajar con dos conjuntos de datos artificiales. Estos son:
make_circles : Este conjunto de datos artificial genera dos variables que
representan dos círculos, uno mayor y otro menor contenido en su interior. Scikit-Learn
permite a su vez introducir algo de ruido sobre las muestras creadas.
In [ ]: # Conjunto de datos sintético circles.
from sklearn.datasets import make_circles

X, y = make_circles(n_samples=600,
noise=0.1,
random_state=0)

In [ ]: # Información general del conjunto.

print(f'X ~ {X.shape[0]} muestras x {X.shape[1]} características.')


print(f'y ~ {y.shape[0]} muestras.')
print('\nPrimeras 5 muestras:\n', X[:5, :])
print('\nPrimeras 5 etiquetas:\n', y[:5])

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 6/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
X ~ 600 muestras x 2 características.
y ~ 600 muestras.

Primeras 5 muestras:
[[-0.79959336 0.23505777]
[-0.8693906 0.56906193]
[ 0.03690973 -0.96481524]
[-0.73333376 -0.60035 ]
[-0.27028346 -0.98249038]]

Primeras 5 etiquetas:
[1 0 0 1 0]

In [ ]: # Graficamos en un área 2d.


plot_data(X, y)

make_moons : Este conjunto de datos artificial genera dos variables asociadas a dos
clases que representan dos medias lunas. Scikit-Learn permite a su vez introducir algo
de ruido sobre las muestras creadas.
In [ ]: #Conjunto de datos sintético moons.
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=600, #Número de observaciones o muestras.


noise=0.15, #Cantidad de ruido aleatorio introducido.
random_state=0 #Semilla aleatoria para garantizar la replicab
)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 7/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: #Información general del conjunto.

print(f'X ~ {X.shape[0]} muestras x {X.shape[1]} características.')


print(f'y ~ {y.shape[0]} muestras.')
print('\nPrimeras 5 muestras:\n', X[:5, :])
print('\nPrimeras 5 etiquetas:\n', y[:5])

X ~ 600 muestras x 2 características.


y ~ 600 muestras.

Primeras 5 muestras:
[[ 0.77172208 -0.52882117]
[ 0.23023466 0.98205132]
[-0.61846873 0.7614099 ]
[ 1.19039559 -0.66801557]
[-0.34596664 0.73132122]]

Primeras 5 etiquetas:
[1 0 0 1 0]

In [ ]: #Graficamos en un área 2d.


plot_data(X, y)

Estos conjuntos de datos producen un resultado interesante cuando se utilizando los


métodos y modelos discutidos hasta ahora. Si aplicamos un modelo de clasificación lineal
como una regresión logística, observamos que es difícil establecer una separación
apropiada:
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 8/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: # Clasificador basado en regresión logística.


from sklearn.linear_model import LogisticRegression

lr_model = LogisticRegression(multi_class='multinomial', solver='lbfgs');


lr_model.fit(X, y)

plot_data(X, y, lr_model)

In [ ]: print("Score:", lr_model.score(X, y))

Score: 0.8883333333333333

Existen problemas en los que generar una clasificación lineal no produce un resultado
apropiado, sin importar el tamaño del conjunto o tiempo invertido en el entrenamiento. Por
lo tanto, surge la necesidad de usar un modelo que permita realizar una clasificación no
lineal.

3. Árboles de Decisión
El primer algoritmo de clasificación no lineal que discutiremos es el basado en árboles de
decisión. Los árboles de decisión son muy intuitivos, pues codifican una serie de
elecciones "si esto" y "si no, entonces", de forma muy similar a como una persona tomaría
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 9/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

una decisión, o un programa simple usando las estructuras if y else . La gran ventaja
de esta técnica es que estas elecciones se pueden aprender de forma automática a partir
de los datos y existen maneras de identificar de manera automática las mejores condiciones
y ramificaciones del árbol generado.
Por ejemplo, considere el siguiente árbol de decisión. Este árbol de decisión describe una
serie de elecciones que buscan determinar si espero (V) o no (F) por una mesa en un
restaurante.

Con base al anterior árbol de decisión, puedo tomar la decisión de esperar o no, usando
unas reglas de clasificación sencillas con preguntas como:
¿Cuántos clientes hay en el restaurante?
¿Cuánto tiempo tengo que esperar?
¿Tengo alguna alternativa de restaurante?
¿Tengo hambre en este momento?
¿Es viernes o sábado?
¿Tengo reservación?
Como puede apreciar, estos árboles pueden tener una interpretación muy intuitiva. Para
tomar una decisión, o más bien realizar una clasificación de la situación, evaluaríamos esta
condición en la observación sobre la que queremos tomar una decisión y avanzamos a la
siguiente condición o estado final. Así, se realizarían preguntas como:
Si Clientes == "Lleno" Y EsperaEstimada == "10-30" Y Hambre ==
"No" Entonces Esperar="SÍ" .
Si Clientes == "Algunos" Entonces Esperar="SI" .
Si Clientes == "Lleno" Y EsperaEstimada == ">60" Entonces
Esperar="NO" .

In [ ]: #@markdown **Video: Ejemplo de predicción con árboles de decisión**


#@markdown ***

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 10/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
from IPython.display import HTML

HTML('<iframe style="width:768px; height: 432px;" src="https://drive.google.com

Out[ ]:

3.1. Ventajas y desventajas


Ventajas
Los datos de entrada requieren muy poco preprocesamiento. Los árboles de decisión
pueden trabajar con variables de diferente tipo (continuas y categóricas) y son
invariantes al escalamiento de las características.
Los modelos son fáciles de interpretar y los árboles pueden ser visualizados.
El costo computacional del uso del árbol para predecir la categoría de un ejemplo es
mínimo comparado con otras técnicas (se realiza en tiempo logarítmico).
Desventajas
Puede ser tan complejo, que se memoriza el conjunto de datos, por lo tanto, no
generaliza tan bien (sobreajuste).
Son muy sensibles al desbalance de clases (sesgo).

3.2. Implementación en Scikit-Learn


file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 11/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

La implementación de Scikit-Learn se consigue con la clase DecisionTreeClassifier


del módulo sklearn.tree :
In [ ]: # Importamos el constructor del clasificador por árboles de decisión.
from sklearn.tree import DecisionTreeClassifier

classifier = DecisionTreeClassifier()

Cargamos de nuevo nuestros conjuntos de datos Iris y moons.


In [ ]: #Conjunto de datos iris
iris = load_iris()

X_iris = iris.data[:,[0, 2]] # Usamos solo dos variables (longitud del sépalo y
y_iris = iris.target

In [ ]: #Conjunto de datos de medias lunas


X_moons, y_moons = make_moons(n_samples=600, # Número de observaciones o muestr
noise=0.3, # Cantidad de ruido aleatorio intr
random_state=0 # Semilla aleatoria para garantiza
)

Algunos de los parámetros más importantes del clasificador DecisionTreeClassifier


son:
max_depth : profundidad máxima del árbol.
criterion : medida para determinar la calidad del particionamiento generado por un
atributo. Soporta el coeficiente de Gini y entropía.
min_samples_split : controla el número mínimo de muestras que debe haber en un
nodo luego de una partición.
min_samples_leaf : controla el número mínimo de muestras que debe haber en un
nodo hoja.
In [ ]: classifier.get_params()

{'ccp_alpha': 0.0,
Out[ ]:
'class_weight': None,
'criterion': 'gini',
'max_depth': None,
'max_features': None,
'max_leaf_nodes': None,
'min_impurity_decrease': 0.0,
'min_samples_leaf': 1,
'min_samples_split': 2,
'min_weight_fraction_leaf': 0.0,
'random_state': None,
'splitter': 'best'}

In [ ]: # Entrenamos el modelo con el conjunto de datos moons.

moons_DT_classifier = DecisionTreeClassifier()
moons_DT_classifier = moons_DT_classifier.fit(X_moons, y_moons)

Visualizamos la superficie de decisión del clasificador


file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 12/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: plot_data(X_moons, y_moons, moons_DT_classifier)

In [ ]: print(f'Error: {1 - moons_DT_classifier.score(X_moons, y_moons)}')

Error: 0.0

Y ahora podemos realizar el mismo proceso con el dataset Iris.


In [ ]: #Entrenamos el modelo con el conjunto de datos moons

iris_DT_classifier = DecisionTreeClassifier()
iris_DT_classifier = iris_DT_classifier.fit(X_iris, y_iris)

Visualizamos la superficie de decisión del clasificador.


In [ ]: plot_data(X_iris, y_iris, iris_DT_classifier)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 13/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: print(f'Error: {1 - iris_DT_classifier.score(X_iris, y_iris)}')

Error: 0.00666666666666671

Ambos conjuntos de datos tienen un error con valor , que apunta a un posible
0

sobreajuste.

3.3. Visualización de árboles de decisión


El árbol de decisión aprendido puede ser visualizado usando la librería de visualización de
grafos graphviz .
Nota: En sistemas operativos basados en Linux como Ubuntu se recomienda
instalarlo usando estas líneas:
sudo apt-get install graphviz
pip install graphviz
pip install pydot

A continuación, vamos a usar el conjunto de datos Iris completo, usando las cuatro
características, y su árbol de decisión, pero esta vez utilizaremos las 4 variables.
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 14/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: # Declaramos los datos de entrada.


X_iris = iris.data
y_iris = iris.target

# Declaramos y entrenamos el clasificador.


iris_DT_classifier = DecisionTreeClassifier(random_state=2)
iris_DT_classifier = iris_DT_classifier.fit(X_iris, y_iris)

Usamos graphviz para visualizar el árbol generado. El método graphviz soporta como
parámetros los nombres de las clases y de las características a graficar. Además, con el
método sklearn.tree.export_graphviz podemos generar la entrada de este método
y así visualizar los modelos que entrenemos.
In [ ]: # Función de utilidad para la visualización de archivos SVG en notebooks.
from IPython.display import SVG

# Importamos la función de generación del archivo de descripción del árbol.


from sklearn.tree import export_graphviz

graphviz_data = export_graphviz(iris_DT_classifier, # Árbol de decisión entrena


out_file=None, # Se usa 'None' para genera
feature_names=iris.feature_names, # Nombre de
class_names=iris.target_names, # Nombre de
# Configuración de estilo.
filled=True, rounded=True, special_characters=T

# Generamos el grafo de graphviz para la visualización.


graph = graphviz.Source(graphviz_data)

# Exportamos en formato svg y visualizamos con IPython.


SVG(graph.pipe(format='svg'))

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 15/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Out[ ]:

Podemos ver como resultó el modelo construido y las reglas que se definieron en la
construcción del árbol. Una cosa a tener en cuenta es la diferenciación inmediata de las
flores de la clase setosa (en naranja) con la primera regla del árbol, en la que para todas
las flores cuyo grosor de pétalo es menor que se clasifican en esta especie.
0.8(cm)

Tras esto, nos podemos imaginar que el grosor de pétalo es especialmente importante para
distinguir las flores setosa. ¿Hay alguna forma de identificar la importancia de cada
variable?

3.4. Importancia de las variables


Una de las ventajas de usar árboles de decisión, es que nos permite determinar la
importancia de cada característica, con base al índice de impureza usado. Scikit-Learn nos
permite acceder a la importancia de cada característica llamando el atributo
feature_importances_ del clasificador. Esta importancia cuantifica qué tanto aporta
cada característica a mejorar el desempeño del árbol.
In [ ]: # Valores entre 0 y 1 del aporte al desempeño de cada característica.
iris_DT_classifier.feature_importances_

array([0.01333333, 0.01333333, 0.05072262, 0.92261071])


Out[ ]:

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 16/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

El atributo feature_importances_ contiene un arreglo de Numpy del tamaño igual al


número de características. Por ejemplo, la importancia de la característica ( sepal 0

length (cm) ) es de . 1.3%

In [ ]: # Nombres de las características a partir del bunch.


iris.feature_names

['sepal length (cm)',


Out[ ]:
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']

Entonces, podríamos decir que, para el modelo construido, las variables aportan a la
decisión de la siguiente forma:
In [ ]: sns.barplot(x = iris.feature_names, # Nombre de las características.
y = iris_DT_classifier.feature_importances_ ); # Importancia de cad

3.5. Evaluación de la complejidad usando


DecisionTreeClassifier

Para evaluar la complejidad, vamos a estimar este valor tomando como referencia la
profundidad del árbol.
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 17/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: # Función de partición en subconjuntos de entrenamiento y pruebas.


from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_iris, y_iris,


test_size=0.3, # Propor
random_state=1234, # Semill
stratify=y_iris) # Estrat

Vamos a explorar los siguientes valores de profundidad máxima:


[1, 2, 3, … , 20]

In [ ]: # Números enteros de 1 a 20 como posibles valores del hiperparámetro de profund


max_depth_values = np.arange(1, 21, 1)

# Arreglos vacíos para almacenar el error de entrenamiento y el de generalizaci


train_error = np.empty(len(max_depth_values))
generalization_error = np.empty(len(max_depth_values))

for depth in max_depth_values:


# Entrenamos un árbol de decisión para cada valor de profundidad.
decision_tree = DecisionTreeClassifier(max_depth=depth)
decision_tree.fit(X_train, y_train)

# Almacenamos el error de entrenamiento y de generalización por cada árbol.


train_error[depth - 1] = (1 - decision_tree.score(X_train, y_train))
generalization_error[depth - 1] = (1 - decision_tree.score(X_test, y_test))

Visualizamos la curva de error de entrenamiento contra error de generalización:


In [ ]: plot_learning_curve(train_error, generalization_error)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 18/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

En este caso, el balance entre ambos errores se alcanza muy rápido, con profundidad de 2

nodos.

4. Máquinas de vectores de soporte (SVM)


Las máquinas de vectores de soporte son un modelo de aprendizaje supervisado en el cual
se busca representar a los ejemplos en un nuevo espacio, de tal forma que a aquellos
ejemplos de diferentes categorías sea posible, en principio, separarlos linealmente en este
nuevo espacio. Considere el siguiente ejemplo, usando el conjunto de datos generados con
el Generator make_circles .
In [ ]: X, y = make_circles(n_samples=1000, factor=.3, noise=.08 , random_state= 0)

Se trata de un conjunto de datos que no es linealmente separable


In [ ]: plot_data(X, y)

¿Se pueden separar las clases con una función lineal?


Si intentamos utilizar un modelo de clasificación lineal, podemos notar que el resultado no
es apropiado. De hecho, este es un ejemplo de problema en el que es imposible distinguir
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 19/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

directamente los datos con un clasificador lineal.


In [ ]: from sklearn.linear_model import LogisticRegression

# Declaramos y entrenamos un modelo de regresión logística.


lr_model = LogisticRegression(multi_class='multinomial', solver='lbfgs');
lr_model.fit(X, y)

# Graficamos la región de decisión.


plot_data(X, y, lr_model)

In [ ]: # Imprimimos el score del modelo.


print("Score:", lr_model.score(X, y))

Score: 0.502

Un modelo SVM crea, implícitamente, un espacio de representación de mayor


dimensionalidad en el cual sí podemos separar de forma clara nuestros datos. Esto se
ilustra en la siguiente figura:

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 20/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Para el ejemplo de los dos círculos, podemos llevar los datos a un nuevo espacio más
apropiado. Para esto, usaremos un espacio donde las características corresponden al
cuadrado de las características originales:
2 2
ϕ :R ⟶R
(1)
2 2
(x, y)⟼(x , y )

In [ ]: X_square = X * X

plot_data(X_square, y)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 21/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

En este nuevo espacio de representación SÍ es posible separar los datos con un


discriminante lineal.
4.1. Kernel trick
SVM usa una función conocida como Kernel. Intuitivamente, esta función define qué tan k

parecidas son dos instancias del conjunto de datos. Formalmente, la función calcula el k

producto punto en el espacio de características donde se representarán los datos.


Dependiendo del kernel, este espacio de características es de mayor dimensionalidad, y
facilita la definición de un "hiperplano" que separe los ejemplos de ambas características.
La dimensión de este hiperplano varía de acuerdo al número de características. Si se tienen
2, el hiperplano es una recta. Si se tienen 3 el hiperplano es un plano en un espacio de 3
dimensiones.
Existen varias opciones para las funciones de kernel. Primero, vamos a cargar nuevamente
los dos conjuntos de datos (iris y moons) sobre los cuales vamos a comparar la superficie
de decisión generada por cada tipo de kernel. Empezaremos de una vez con la división en
grupos de entrenamiento y evaluación.
In [ ]: # Dataset de flores iris
iris = load_iris()

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 22/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
X_iris = iris.data[:,[0, 2]]
y_iris = iris.target
X_train_iris, X_test_iris, y_train_iris, y_test_iris = train_test_split(X_iris,
y_iris,
test_si
random_

In [ ]: # Dataset sintético de lunas


X_moons, y_moons = make_moons(n_samples=600, noise=0.3, random_state=0)
X_train_moons, X_test_moons, y_train_moons, y_test_moons = train_test_split(X_m
y_m
tes
ran

In [ ]: fig, (ax1, ax2) = plt.subplots(ncols = 2, figsize = (10, 4))


plot_data(X_iris, y_iris, ax = ax1, title = 'Conjunto de datos Iris')
plot_data(X_moons, y_moons, ax = ax2, title = 'Conjunto de datos Moons')

4.1.1. Kernel Lineal


En un kernel lineal, la función está definida como:
k

T
k(x, y) = ⟨x, y⟩ = xy

Esta implementación puede ser consultada a través de sklearn.svm.LinearSVC .


In [ ]: # Modelo de máquina de vector de soporte con kernel lineal.
from sklearn.svm import LinearSVC

# Declaramos el modelo SVC lineal para ambos conjuntos de datos.


linear_iris = LinearSVC(max_iter=5000)
linear_moons = LinearSVC(max_iter=5000)

# Entrenamos cada modelo con sus datos de entrada respectivos.


linear_iris.fit(X_train_iris, y_train_iris)
linear_moons.fit(X_train_moons, y_train_moons);

Ahora visualizamos los datos de ambos:


file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 23/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: plot_data(X_test_iris, y_test_iris, linear_iris)

El error en el conjunto de entrenamiento y prueba es el siguiente:


In [ ]: print(f"Error en entrenamiento:\t{1-linear_iris.score(X_train_iris, y_train_iri
print(f"Error en prueba:\t{1-linear_iris.score(X_test_iris, y_test_iris):.4f}")

Error en entrenamiento: 0.0476


Error en prueba: 0.1111

Ahora hacemos los mismo para el conjunto de datos generado artificialmente:


In [ ]: plot_data(X_moons, y_moons, linear_moons)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 24/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: print(f"Error en entrenamiento:\t{1-linear_moons.score(X_train_moons, y_train_m


print(f"Error en prueba:\t{1-linear_moons.score(X_test_moons, y_test_moons):.4f

Error en entrenamiento: 0.1238


Error en prueba: 0.1500

Hasta el momento, los resultados son muy similares a los que obtendríamos con otro
método de clasificación lineal.
4.1.2. Kernel Gaussiano
Otro kernel muy importante es el kernel gaussiano. Este está definido por la siguiente
función:
′ 2
∥x − x ∥

K(x, x ) = exp(− )
2

la cual se puede simplificar como


′ ′ 2
K(x, x ) = exp(−γ∥x − x ∥ )

1
γ =
2

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 25/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

En la literatura este método también se encuentra como kernel usando una función de base
radial (RBF por del ingles Radial basis function).
Para implementar un clasificador de vectores de soporte con un kernel gaussiano podemos
utilizar el constructor de clasificadores basados en máquinas de soporte general
sklearn.SVC . Esta es una versión general de este tipo de clasificador, que acepta
distintos argumentos para definir el método deseado.
En el siguiente ejemplo probamos con un valor pequeño del argumento ( gamma ): γ

In [ ]: #Clasificador de vectores de soporte general.


from sklearn.svm import SVC

rbf_svm = SVC(kernel='rbf', # Kernel de tipo RBF


gamma = 0.001) # Valor del argumento gamma

rbf_svm.fit(X_train_moons, y_train_moons);

In [ ]: plot_data(X_test_moons, y_test_moons, rbf_svm)

Reportamos el error de entrenamiento y prueba:


In [ ]: print(f"Error en entrenamiento:\t {1-rbf_svm.score(X_train_moons, y_train_moons
print(f"Error en prueba:\t {1-rbf_svm.score(X_test_moons, y_test_moons):.4f}")

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 26/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
Error en entrenamiento: 0.2000
Error en prueba: 0.2111

El resultado no es mucho mejor al obtenido previamente. Usemos ahora un más grande. γ

In [ ]: # Creamos y entrenamos el modelo.


rbf_svm = SVC(kernel='rbf',
gamma = 1000)
rbf_svm.fit(X_train_moons, y_train_moons)

# Graficamos los resultados.


plot_data(X_test_moons, y_test_moons, rbf_svm)

Ahora reportamos el error de entrenamiento y prueba:


In [ ]: print(f"Error en entrenamiento:\t{1-rbf_svm.score(X_train_moons, y_train_moons)
print(f"Error en prueba:\t{1-rbf_svm.score(X_test_moons, y_test_moons):.4f}")

Error en entrenamiento: 0.0000


Error en prueba: 0.2278

La forma ha mejorado, pero ahora tenemos franjas de la clase ocupando el espacio que 0

debería corresponder a la clase , es decir, el modelo está sobreajustado. Probemos ahora


1

con un valor de intermedio:


γ

In [ ]: # Creamos y entrenamos el modelo.


rbf_svm = SVC(kernel='rbf',

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 27/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
gamma = 0.7)
rbf_svm.fit(X_train_moons, y_train_moons)

# Graficamos los resultados.


plot_data(X_test_moons, y_test_moons, rbf_svm)

In [ ]: print(f"Error en entrenamiento:\t{1-rbf_svm.score(X_train_moons, y_train_moons)


print(f"Error en prueba:\t{1-rbf_svm.score(X_test_moons, y_test_moons):.4f}")

Error en entrenamiento: 0.0881


Error en prueba: 0.1444

Probamos el mismo kernel sobre el conjunto de datos Iris:


In [ ]: rbf_svm = SVC(kernel='rbf', gamma = 0.7)
rbf_svm.fit(X_train_iris, y_train_iris)

plot_data(X_test_iris, y_test_iris, rbf_svm)

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 28/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Finalmente reportamos el error en entrenamiento y prueba:


In [ ]: print(f"Error en entrenamiento:\t{1-rbf_svm.score(X_train_iris, y_train_iris):.
print(f"Error en prueba:\t{1-rbf_svm.score(X_test_iris, y_test_iris):.4f}")

Error en entrenamiento: 0.0286


Error en prueba: 0.0222

5.modelo
Estimación de los hiperparámetros del
Hasta el momento nos hemos concentrado en evaluar nuestros modelos en una partición de
prueba. Sin embargo, es común introducir sobreajuste a través de la modificación manual
de los hiperparámetros de un modelo conforme vamos reportando el error de
generalización sobre el conjunto de prueba.

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 29/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal
Usar la mejor
configuración y entrenar
un modelo sobre la
unión de
"Entrenamiento" y
Reportar el "Validación" Reportar el
Generar n configuraciones
desempeño de cada
para un modelo. Entrenar un
configuración en desempeño
modelo por cada configuración
validación sobre Prueba

Train Val Test

Conjunto de datos
En la anterior imagen, introducimos una nueva partición, adicional a la de prueba y
entrenamiento, conocida como partición de "validación". Esta partición es resultado de
tomar la partición de entrenamiento y volver a dividirla (en entrenamiento y validación) de
tal forma que cualquier configuración de parámetros que se use para entrenar un modelo,
pueda ser reportada en validación. Una vez estemos seguros de que tenemos el modelo
con el mejor desempeño en validación, volvemos a unir ambas particiones, entrenamos un
modelo sobre la partición original de entrenamiento y reportamos una sola vez en el
conjunto de prueba.

5.1. Validación cruzada de pliegues k

A pesar de que se introdujo una nueva partición para validar los parámetros de un modelo,
se sigue usando una partición reducida para entrenar el conjunto de datos. La validación
cruzada nos permite usar una mayor parte de los datos para construír el modelo y generar
un estimador más robusto y con mayor capacidad de generalización. En validación cruzada,
los datos son particionados varias veces en entrenamiento y validación de forma repetida.
Finalmente, el desempeño del clasificador es agregado sobre las diferentes particiones de
validación para obtener un estimador más robusto.
La validación cruzada se hace comúnmente de la siguiente manera:
1. Se divide el conjunto de entrenamiento en pliegues o particiones (usualmente 3, 5 o
k

10). Estas particiones deben ser del mismo tamaño.


2. En cada iteración, uno de los pliegues es usado como la partición de validación,
mientras el resto es usado como la partición de entrenamiento.
3. Se reporta y guarda el desempeño sobre esa partición de validación.

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 30/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Conjunto de datos (Entrenamiento)

k=1

k=2

k=3

k=4 Entrenamiento

Validación
k=5

5.1.1. Validación cruzada usando Scikit-Learn


Scikit-Learn provee una implementación muy eficiente para realizar validación cruzada
usando métodos del módulo sklearn.model_selection . El método
sklearn.model_selection.cross_val_score recibe un estimador y un conjunto de
datos, luego hace el particionamiento y entrena un modelo sobre cada partición de
validación.
In [ ]: X_iris = iris.data
y_iris = iris.target

classifier_iris = LinearSVC()

El parámetro cv en cross_val_score controla el número de pliegues a usar.


In [ ]: # Desempeño por validación cruzada.
from sklearn.model_selection import cross_val_score

scores = cross_val_score(classifier_iris,
X_iris,
y_iris,
cv = 5)

In [ ]: print(f'Accuracy por cada pliegue:\n{list(scores)}')


print(f'Accuracy promedio sobre los 5 pliegues: {np.mean(scores)}')

Accuracy por cada pliegue:


[1.0, 1.0, 0.9333333333333333, 0.9, 1.0]
Accuracy promedio sobre los 5 pliegues: 0.9666666666666668
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 31/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

cross_val_score realiza por defecto una partición estratificada usando


sklearn.model_selection.StratifiedKFold . Esta estrategia consiste en hacer un
particionamiento de tal forma que cada partición tenga la misma distribución de etiquetas . y

En caso de que se quiera hacer una partición diferente, se puede especificar como
argumento cv otro modelo de validación cruzada. Por ejemplo, si no queremos hacer
partición estratificada podemos usar el constructor sklearn.model_selection.KFold .
Una vez declarado un objeto de tipo KFold , podemos usar el método split para iterar
por los índices de las particiones y para cada pliegue autogenerado.
X y

A continuación, agregaremos el conteo de clases por cada pliegue y obtendremos el score


obtenido mediante el método cross_val_score .
In [ ]: # Validación cruzada de K-pliegues.
from sklearn.model_selection import KFold, StratifiedKFold
n_folds = 5 # Definimos un total de 5 pliegues.

cv_no_stra = KFold(n_splits= n_folds)

X = iris.data
y = iris.target

pd.DataFrame(# Conteo de valores por clase. Por los índices de cada pliegue gen
[np.bincount(y[y_index], minlength = 3)
for X_index, y_index in cv_no_stra.split(X, y)]
# Renombramos los índices (pliegues) y columnas (clases).
).rename(index = lambda x: f'Pliegue {x}',
columns = lambda x: f'Clase {x}'
# Creamos una columna para almacenar el accuracy obtenido en cada
).assign(accuracy = cross_val_score(classifier_iris, X_iris, y_ir

Out[ ]: Clase 0 Clase 1 Clase 2 accuracy


Pliegue 0 30 0 0 1.000000
Pliegue 1 20 10 0 1.000000
Pliegue 2 0 30 0 0.666667
Pliegue 3 0 10 20 0.966667
Pliegue 4 0 0 30 0.666667

5.2. Validación cruzada con Grid Search


Los modelos SVM que usan RBF comúnmente requieren el ajuste de los parámetros gamma
(coeficiente del kernel) y C (parámetro de regularización.
γ

Ambos parámetros controlan la complejidad del modelo. Estos parámetros pueden ser
explorados usando un retículo (grid) de valores y evaluando su desempeño usando
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 32/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

validación cruzada de pliegues. A continuación, creamos una partición entrenamiento y


k

prueba sobre el conjunto de datos Iris.


In [ ]: #@markdown **Video: Experimento con *Grid Search***
from IPython.display import HTML

HTML('<iframe style="width:768px; height: 432px;" src="https://drive.google.com

Out[ ]:

In [ ]: # Partición estratíficada del conjunto de datos Iris.


X_train, X_test, y_train, y_test = train_test_split(X_iris,
y_iris,
test_size=0.3,
random_state=1,
stratify=y_iris)

Definimos los siguientes valores para y C gamma . Vamos a explorar estos valores en el
siguiente rango de potencias de 2:
−5 −4 6 7
2 ,2 ,…,2 ,2

In [ ]: # Los hiperparámetros deben estar en forma de diccionario.


param_grid = {'C': [2**i for i in range(-5, 7, 1)],
'gamma': [2**i for i in range(-5, 7, 1)]}

param_grid

{'C': [0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64],


Out[ ]:
'gamma': [0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64]}

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 33/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Valores de : C

In [ ]: print(param_grid['C'])

[0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64]

Valores de gamma :
In [ ]: print(param_grid['gamma'])

[0.03125, 0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 64]

Podemos realizar una validación cruzada con grid search en los hiperparámetros e
identificar la configuración con el mejor score utilizando el método
sklearn.model_selection.GridSearchCV . El objeto retornado por este método
funciona como los demás modelos e implementa funciones como fit , score y
predict .

GridSearchCV recibe tres argumentos principales:

estimator : modelo de Scikit-Learn a explorar. En este ejemplo usaremos


SVC(kernel='rbf') .
param_grid : diccionario que contiene los parámetros que se van a explorar usando
validación cruzada. La llave del diccionario es el nombre del parámetro y el valor es una
lista con los valores que se quieren explorar.
return_train_score : si se ingresa como argumento el valor True , los puntajes o
scores de los modelos entrenados en cada combinación aparecerán en el objeto del
atributo cv_results_ .
In [ ]: # Búsqueda en cuadrícula de hiperparámetros.
from sklearn.model_selection import GridSearchCV

grid_clf = GridSearchCV(SVC(kernel='rbf'),
param_grid=param_grid,
verbose=1,
return_train_score=True
)

grid_clf.fit(X_train, y_train)

Fitting 5 folds for each of 144 candidates, totalling 720 fits


Out[ ]: ▸ GridSearchCV
▸ estimator: SVC

▸ SVC

GridSearchCV nos ofrece una serie de atributos y métodos que nos permiten consultar:
La lista de resultados por elemento en la malla de parámetros( cv_results_ ).
La configuración con el mejor desempeño ( best_params_ ).
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 34/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

El accuracy promedio sobre todos los pliegues de la mejor


configuración( best_score_ ).
Los valores promedio de accuracy para cada combinación de hiperparámetros se pueden
extraer usando GridSearchCV.cv_results_ . Para visualizarlo mejor convertimos ese
diccionario a un DataFrame de Pandas.
In [ ]: cv_results = pd.DataFrame(grid_clf.cv_results_)
cv_results

Out[ ]: mean_fit_time std_fit_time mean_score_time std_score_time param_C param_gamma pa

0 0.001900 0.000125 0.001245 0.000364 0.03125 0.0


0.03125 'gam
0.03

1 0.002770 0.001625 0.001104 0.000117 0.03125 0.0


0.0625 'gam
0.0

2 0.001998 0.000246 0.001704 0.001261 0.03125 0.0


0.125 'gam
0

3 0.002535 0.001179 0.001117 0.000070 0.03125 0.0


0.25 'gam

4 0.001974 0.000178 0.001021 0.000022 0.03125 0.0


0.5 'gam

... ... ... ... ... ... ...


{'C
139 0.001215 0.000012 0.000651 0.000067 64 4 'gam
{'C
140 0.001870 0.000312 0.001009 0.000046 64 8 'gam
{'C
141 0.001415 0.000163 0.000653 0.000028 64 16 'gam
{'C
142 0.001312 0.000011 0.000643 0.000015 64 32 'gam
{'C
143 0.001574 0.000526 0.000667 0.000037 64 64 'gam

144 rows × 22 columns

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 35/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Para encontrar las mejores configuraciones, podemos obtener la tabla de los mayores n

resultados con pandas.


El método nlargest es un método de los objetos DataFrame de pandas que
permite retornar las observaciones con mayor valor en la variable deseada.
n

En este caso esa variable es el puntaje del modelo mean_test_score .


In [ ]: # Método nlargest de pandas, con los 10 primeros valores por mean_test_score.
cv_results.nlargest(10, 'mean_test_score')

Out[ ]: mean_fit_time std_fit_time mean_score_time std_score_time param_C param_gamma pa


{
65 0.001464 0.000032 0.000888 0.000017 1 1 'gam
{
76 0.000969 0.000054 0.000643 0.000101 2 0.5 'gam
{'
97 0.000800 0.000012 0.000547 0.000015 8 0.0625 'gam
0.0
{'C
108 0.000801 0.000010 0.000546 0.000015 16 0.03125 'gam
0.03
{'C
52 0.001551 0.000171 0.000895 0.000035 0.5 0.5 'gam
{'C
53 0.001482 0.000033 0.000886 0.000025 0.5 1 'gam
{'C
54 0.001886 0.000443 0.000987 0.000058 0.5 2 'gam
{
63 0.001377 0.000051 0.000910 0.000027 1 0.25 'gam
{
73 0.001465 0.000124 0.000921 0.000032 2 0.0625 'gam
0.0
{
74 0.001432 0.000027 0.000932 0.000020 2 0.125 'gam
0
10 rows × 22 columns
En este caso la configuración con y es la que aparece como mayor puntaje,
C = 1 γ = 1

empatada con otra configuración con y . Esta información también se


C = 2 γ = 0.25

puede consultar usando .best_params_ y .best_score_ .

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 36/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

In [ ]: # Mejores parámetros identificados.


print(grid_clf.best_params_)

{'C': 1, 'gamma': 1}

In [ ]: # Puntaje de la mejor combinación de parámetros.


print(grid_clf.best_score_)

0.9619047619047618

Una vez se haya entrenado el modelo usando validación cruzada, GridSearchCV escoge
automáticamente la mejor configuración y vuelve a entrenar un modelo sobre todo el
conjunto de datos de entrenamiento. Por lo tanto tras realizar el entrenamiento con fit se
pueden hacer llamados a funciones como predict() y score() directamente desde el
objeto de grid search.
Para reportar sobre el conjunto de prueba basta con ejecutar:
In [ ]: grid_clf.score(X_test, y_test)

0.9777777777777777
Out[ ]:

A continuación, vamos a explorar gráficamente los resultados obtenidos en todas las


configuraciones. Primero observamos que el número de filas del DataFrame cv_results
corresponde al número de configuraciones de hiperparámetros que se están explorando:
In [ ]: len(cv_results), len(param_grid['C']) * len(param_grid['gamma'])

(144, 144)
Out[ ]:

Usando la columna mean_test_score , extraemos los valores de precisión o accuracy


promedio y los organizamos en una matriz donde las filas corresponden a los valores del
parámetro C y las columnas a los valores del parámetro gamma .
Para esto, usaremos el método pivot de pandas que genera una tabla de pivote.
In [ ]: scores_df = cv_results.pivot(index = 'param_C',
columns = 'param_gamma',
values = 'mean_test_score')
scores_df

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 37/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Out[ ]: param_gamma 0.03125 0.06250 0.12500 0.25000 0.50000 1.00000 2.00000 4.0000
param_C
0.03125 0.914286 0.923810 0.923810 0.914286 0.923810 0.933333 0.914286 0.8857
0.06250 0.914286 0.923810 0.923810 0.914286 0.923810 0.933333 0.914286 0.8857
0.12500 0.914286 0.923810 0.914286 0.923810 0.933333 0.942857 0.914286 0.8857
0.25000 0.923810 0.914286 0.942857 0.914286 0.942857 0.942857 0.914286 0.91428
0.50000 0.914286 0.942857 0.923810 0.942857 0.952381 0.952381 0.952381 0.9238
1.00000 0.942857 0.933333 0.942857 0.952381 0.942857 0.961905 0.942857 0.91428
2.00000 0.933333 0.952381 0.952381 0.942857 0.961905 0.942857 0.933333 0.90476
4.00000 0.952381 0.952381 0.952381 0.952381 0.942857 0.933333 0.933333 0.90476
8.00000 0.952381 0.961905 0.952381 0.952381 0.942857 0.923810 0.933333 0.89523
16.00000 0.961905 0.952381 0.952381 0.942857 0.914286 0.933333 0.933333 0.89523
32.00000 0.952381 0.952381 0.933333 0.923810 0.914286 0.933333 0.923810 0.89523
64.00000 0.952381 0.942857 0.923810 0.923810 0.923810 0.923810 0.923810 0.89523

A continuación, presentamos una forma de visualizar esta exploración sobre la malla de


hiperparámetros en forma de mapa de calor con el método heatmap de Seaborn, una
librería de visualización de datos estadísticos.
In [ ]: sns.heatmap(scores_df, cmap = 'inferno').set_title('Accuracy en validación');

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 38/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn en la
creación y entrenamiento de modelos de clasificación de lineal, validación cruzada y
búsqueda de hiperparámetros, además de material de apoyo teórico para reforzar estos
conceptos:
mlcourse.ai - Topic 3. Classification, Decision Trees and k Nearest Neighbors
Curso de aprendizaje automático para el INE- Support Vector Machines
A Gentle Introduction to k-fold Cross-Validation
scikit-learn - 3.2.1. Exhaustive Grid Search

Créditos
Profesor: Fabio Augusto Gonzalez
file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 39/40
7/5/23, 16:33 M2U3_Métodos_de_clasificación_no_lineal

Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería

file:///Users/mac/Downloads/M2U3_Métodos_de_clasificación_no_lineal.html 40/40
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Análisis de
neuronales series de tiempo con redes
En este material se presentarán las redes neuronales y, en particular, la implementación de
un perceptrón multicapa con Scikit-Learn. Además, y para poner a prueba este tipo de
modelo, se planteará un problema de análisis de series de tiempo, y se entrenará y evaluará
un modelo para este proceso.

1. Dependencias
Importamos las librerías necesarias y definimos algunas funciones básicas de visualización
que vamos a usar en algunos ejemplos.
1.1. Dependencias
Para la construcción de modelos y ejecución de procedimientos metodológicos de
aprendizaje automático, utilizaremos la librería Scikit-learn ( sklearn ) y varias de sus
funciones y conjuntos de datos.
In [ ]: # Actualizamos scikit-learn a la última versión
!pip install -U scikit-learn

# Importamos scikit-learn
import sklearn

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 1/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w
heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-p
ackages (1.2.2)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-p
ackages (from scikit-learn) (1.10.1)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.1.1)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.22.4)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn) (3.1.0)

Importamos además algunas librerías básicas y configuraciones de Python.


In [ ]: # Librerías básicas NumPy, Pandas, Matplotlib y Seaborn.
import numpy as np
import pandas as pd
import matplotlib as mpl
import seaborn as sns
from matplotlib import pyplot as plt

In [ ]: # Configuraciones para las librerías y módulos usados.

# Ignoramos las advertencias o warnings.


import warnings
warnings.simplefilter(action='ignore')

# Configuramos el formato por defecto de la


# librería de visualización Matplotlib.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
mpl.rcParams['figure.dpi'] = 105
mpl.rcParams['figure.figsize'] = (9, 7)

Utilizaremos Plotly para generar visualizaciones interactivas.


In [ ]: !pip install -U plotly
import plotly
import plotly.graph_objects as go

Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w


heels/public/simple/
Requirement already satisfied: plotly in /usr/local/lib/python3.9/dist-package
s (5.13.1)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.9/dis
t-packages (from plotly) (8.2.2)

Este material se realizó con las siguientes versiones:


Python: 3.7.10
Scikit-learn: 0.24.1
NumPy: 1.19.5
Pandas: 1.1.5
Matplotlib: 3.2.2
Seaborn: 0.11.1
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 2/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Plotly: 4.14.3
In [ ]: # Versión de Python y las demás librerías.
!python --version
print('Scikit-learn', sklearn.__version__)
print('NumPy', np.__version__)
print('Pandas', pd.__version__)
print('Matplotlib', mpl.__version__)
print('Seaborn', sns.__version__)
print('Plotly', plotly.__version__)

Python 3.9.16
Scikit-learn 1.2.2
NumPy 1.22.4
Pandas 1.4.4
Matplotlib 3.7.1
Seaborn 0.12.2
Plotly 5.13.1

1.2. Funciones de utilidad y visualización


Para ilustrar los ejemplos discutidos en este material utilizaremos algunas funciones que
permiten visualizar de manera general los datos, junto a las funciones de predicción
obtenidas con cada modelo.
Nota: Matplotlib y Seaborn se encuentran por fuera del alcance de este
módulo. No es necesario que entienda estas funciones en detalle para sacar
partido del resto del contenido puesto a su disposición. Usted decide si leer o
no estas funciones en profundidad. Si decide omitir esta sección, continúe
directamente con la siguiente sección, en donde se discutirán los conjuntos
de datos que vamos a utilizar.
In [ ]: # Función para visualizar un conjunto de datos de dos variables en un plano 2D.
def plot_data(X, y, model = None, ax = None, title=None):

if ax is None:
_, ax = plt.subplots(dpi = 110)

if model is not None:


pred_fun = gen_pred_fun(model)
plot_decision_region(X, pred_fun, ax)

y_unique = np.unique(y)
df = pd.DataFrame({'x1': X[:,0], 'x2': X[:,1], 'Clases': y})
sns.set_theme()
sns.scatterplot(data = df, x = 'x1', y = 'x2',
hue = 'Clases',style = 'Clases', ax = ax, palette = 'Set1')

In [ ]: # Función para visualizar la superficie de decisión de un clasificador.


def plot_decision_region(X, pred_fun, ax=None):
min_x, max_x = np.min(X[:, 0]), np.max(X[:, 0])
min_y, max_y = np.min(X[:, 1]), np.max(X[:, 1])

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 3/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
min_x = min_x - (max_x - min_x) * 0.05
max_x = max_x + (max_x - min_x) * 0.05
min_y = min_y - (max_y - min_y) * 0.05
max_y = max_y + (max_y - min_y) * 0.05

x_vals = np.linspace(min_x, max_x, 100)


y_vals = np.linspace(min_y, max_y, 100)

XX, YY = np.meshgrid(x_vals, y_vals)


grid_r, grid_c = XX.shape

ZZ = np.zeros((grid_r, grid_c))

for i in range(grid_r):
for j in range(grid_c):
ZZ[i, j] = pred_fun(XX[i, j], YY[i, j])

cs = ax.contourf(XX, YY, ZZ, 100, cmap = plt.cm.Pastel1, vmin = 0, vmax = n


ax.get_figure().colorbar(cs, ax=ax, )
ax.set_xlabel("x")
ax.set_ylabel("y")

In [ ]: #Función para generar la función de predicción de un clasificador entrenado pre


def gen_pred_fun(clf):
def pred_fun(x1, x2):
x = np.array([[x1, x2]])
return clf.predict(x)[0]
return pred_fun

In [ ]: # Gráficar la predicción de los datos de temperatura.

def plot_prediction(params, ys, test_date_index):

train_data = mintemp.loc[:test_date_index[0]]
_y_test, _y_forward, _y_last = ys
# Graficamos los valores predichos.
fig = go.Figure(layout = dict(
title = f'<b>Temperaturas medias semanales (1981-1990)</b> <br> {params}
dragmode= 'pan', width = 1200, height = 600))

fig.add_trace(go.Scatter(x = train_data.index, # Datos originales hasta la p


y = train_data.values, # Datos originales hasta la pr
mode = 'lines',
name = 'Valores de entrenamiento y pruebas'))

#Gráfica de los valores de prueba reales.


fig.add_trace(go.Scatter(x = test_date_index,
y = _y_test,
mode='lines+markers',
name='Valores reales (y)'))

#Gráfica de los valores predichos a partir de las ventanas de X_test.


fig.add_trace(go.Scatter(x = test_date_index,
y = _y_forward,
mode = 'lines+markers',
name = 'Valores predichos a partir de datos reales'))

#Gráfica de los valores predichos a partir de ventanas creadas proceduralment

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 4/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
fig.add_trace(go.Scatter(x = test_date_index,
y = _y_last,
mode='lines+markers',
name='Valores predichos a partir de datos predichos')

fig.show(config = dict({'scrollZoom': True}))

2. Conjuntos de datos
Para los ejemplos desarrollados en el transcurso de material, se usarán datos de Scikit-
Learn de carácter real (usando Loaders) y sintético (usando Generators).

2.1. Conjunto de datos Iris


En este material retomaremos el conjunto de datos Iris para ilustrar algunos ejemplos. Para
esto, usaremos la función load_iris de Scikit-Learn.
In [ ]: # Loader del dataset iris
from sklearn.datasets import load_iris

iris = load_iris()

X = iris.data
y = iris.target

In [ ]: # Información general del conjunto.

print(f'X ~ {X.shape[0]} muestras x {X.shape[1]} características.')


print(f'y ~ {y.shape[0]} muestras.')
print('\nPrimeras 5 muestras:\n', X[:5, :])
print('\nPrimeras 5 etiquetas:\n', y[:5])

X ~ 150 muestras x 4 características.


y ~ 150 muestras.

Primeras 5 muestras:
[[5.1 3.5 1.4 0.2]
[4.9 3. 1.4 0.2]
[4.7 3.2 1.3 0.2]
[4.6 3.1 1.5 0.2]
[5. 3.6 1.4 0.2]]

Primeras 5 etiquetas:
[0 0 0 0 0]

In [ ]: #Graficamos en un área 2d
plot_data(X, y)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 5/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

2.2. Conjunto de datos sintético


Además de Iris, vamos a trabajar con el conjunto de datos artificiales moons. Para cargarlo
usaremos la siguiente función:
make_moons : Este conjunto de datos artificial genera dos variables asociadas a dos
clases que representan dos medias lunas. Scikit-Learn permite a su vez introducir algo
de ruido sobre las muestras creadas.
In [ ]: # Conjunto de datos sintético moons.
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=600,
noise=0.1,
random_state=0)

In [ ]: # Información general del conjunto.


print(f'X ~ {X.shape[0]} muestras x {X.shape[1]} características.')
print(f'y ~ {y.shape[0]} muestras.')
print('\nPrimeras 5 muestras:\n', X[:5, :])
print('\nPrimeras 5 etiquetas:\n', y[:5])

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 6/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
X ~ 600 muestras x 2 características.
y ~ 600 muestras.

Primeras 5 muestras:
[[ 0.79376821 -0.51480339]
[ 0.24846934 0.97421613]
[-0.6473948 0.74392718]
[ 1.22860278 -0.596126 ]
[-0.41419323 0.76579368]]

Primeras 5 etiquetas:
[1 0 0 1 0]

In [ ]: # Graficamos en un área 2d.


plot_data(X, y)

2.3. Conjunto de datos de temperaturas mínimas


diarias
Para este material, en la sección de análisis de series de tiempo, utilizaremos el dataset
Minimum Daily Temperatures. Este dataset almacena la temperatura mínima diaria (C°) en
la ciudad de Melbourne, Australia en un periodo de 10 años, desde 1981 hasta 1990. En esta
ocasión usaremos una versión distinta, que toma las medias semanales de temperatura
mínima de estos datos.

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 7/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Estos datos fueron tomados del siguiente enlace de Machine Learning


Mastery, reprocesados con las medias semanales y fueron recopilados
originalmente por Australian Bureau of Meteorology. La siguiente celda
descarga el archivo:
In [ ]: #@markdown **Ejecute esta celda para descargar el archivo en una serie.**

url = 'https://drive.google.com/uc?export=download&id=1XvKsdBs6EG463iN3L1lQv9JX

mintemp = pd.read_csv(url, index_col = 0, parse_dates= True, squeeze = True)


mintemp.head()

Date
Out[ ]:
1981-01-01 20.700000
1981-01-08 16.585714
1981-01-15 19.214286
1981-01-22 18.514286
1981-01-29 16.814286
Name: Temp, dtype: float64

3. Red neuronal artificial


El perceptrón multicapa (Multilayer Perceptron, MLP en inglés) es un tipo especial de red
neuronal artificial en el cual se apilan varias capas de neuronas artificiales o perceptrones.
En inglés también se le denomina como Feedforward neural network.
El perceptrón multicapa está motivado por la poca capacidad del perceptrón sencillo de
modelar funciones no lineales. En un MLP agrupamos tantas capas como necesitemos.

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 8/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Una red neuronal multicapa está generalmente conformada por:


Capa de entrada: recibe los datos de entrada.
Capa oculta: cuenta con una o más neuronas.
Capa de activación: aplica una función de activación sobre la salida de cada neurona
de la capa oculta.
Cada de salida: produce la predicción para completar la tarea supervisada. Puede ser
de clasificación o regresión.

3.1. Implementación con Scikit-Learn


Scikit-Learn nos permite trabajar con redes de perceptrones multicapa usando
sklearn.neural_network.MLPClassifier . Una red multicapa se puede entrenar
usando gradiente descendente, y por tanto, se pueden usar métodos como gradiente
descendente estocástico ( sgd ) u otros métodos de optimización. Cómo se describió en
materiales anteriores, el valor del gradiente determina qué tanto debo modificar los pesos
de la red o parámetros entrenables para resolver la tarea de predicción.
MLPClassifier permite definir esta arquitectura de la siguiente manera:

Capa de entrada: El tamaño de la capa de entrada es definido cuando se llama a la


función fit() . El tamaño está definido por el número de características (o columnas)
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 9/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

de la matriz de características X .
Capa(s) ocultas: El número de capas ocultas y el tamaño de cada una es definido por
el parámetro hidden_layer_size de la clase MLPClassifier . Este parámetro
consiste en una tupla de elementos, donde es igual al número de capas ocultas.
n n

Cada elemento de la tupla determina el número de neuronas de esa capa oculta.


Función de activación: Esta función se define usando el parámetro activation .
Este parámetro puede tomar como valor las cadenas de texto logistic , relu ,
tanh o identity .

In [ ]: #@markdown **Visualización**: funciones de activación posibles como hiperparáme


# Funciones de activación posibles

from google.colab import widgets


tb = widgets.TabBar(['logistic', 'relu', 'tanh', 'identity'])

x = np.linspace(-5,5, 1000)

logistic = 1 / (1 + np.exp(-x))
relu = np.maximum(0, x)
tanh = np.tanh(x)
identity = x

functions = [logistic, relu, tanh, identity]


titles = [r'Sigmoide logístico - $\frac{1}{1 + \exp{-x}}$',
r'ReLU - $max(x, 0)$',
'Tangente hiperbólico - $tanh(x)$',
'Identidad - $x$']

font = {'family': 'serif',


'weight': 'normal',
'size': 16}

for i, function, title in zip(range(4), functions, titles):


with tb.output_to(i, select= (i < 1)):
fig, ax = plt.subplots(figsize = (9, 7), dpi = 105)
ax.plot(x, function); ax.set_title(title, fontdict= font);

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 10/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 11/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 12/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 13/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Capa de salida: Se define cuando se llama a la función fit() . La capa de salida


contiene una única neurona, cuya función de activación se define con base al tipo de
tarea de clasificación. Si es una tarea de clasificación binaria usa una función de
activación logística. Si la tarea es multiclase se usa una función de activación SoftMax.
Adicionalmente MLPClassifier recibe los siguientes parámetros:
solver : Puede ser lbfgs , sgd o adam . El solver lbfgs es una técnica de
optimización muy útil para conjuntos pequeños, mientras que sgd y adam usan
gradiente descendente estocástico y son apropiados para conjuntos de datos grandes.
alpha : El parámetro representa el parámetro de regularización y permite penalizar
α

aquellos pesos grandes. w

In [ ]: # Cargamos los datos en formato X, y


iris = load_iris()
X_iris = iris.data[:,[0, 2]]
y_iris = iris.target

X_moons, y_moons = make_moons(n_samples=600, # Número de observaciones o mues


noise=0.15, # Cantidad de ruido aleatorio in
random_state=0) # Semilla aleatoria para garanti

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 14/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

In [ ]: # Clasificador basado en una red neuronal multicapa.


from sklearn.neural_network import MLPClassifier

# Clasificador para el conjunto Iris


clf_iris = MLPClassifier(solver='sgd', # Descenso del gradiente estocástico.
learning_rate='constant', # Tipo de tasa de aprendizaje.
learning_rate_init=0.001, # Tasa de aprendizaje inicial.
activation='relu', # Función de activación.
# En este caso se usa la función de rect
# lineal uniforme "y = max(0, x)"
max_iter=1000, # Iteraciones máximas
tol= 1e-4, # Valor de tolerancia de la optimizació
hidden_layer_sizes=(10, 10)) # Tamaño de las capas ocultas.

In [ ]: # Entrenamiento del modelo. Este proceso puede tardar.

clf_iris.fit(X_iris, y_iris)

Out[ ]: ▾ MLPClassifier
MLPClassifier(hidden_layer_sizes=(10, 10), max_iter=1000, solver='sg
d')

Podemos observar la evolución del error de entrenamiento durante el proceso de


optimización usando el atributo loss_curve .
In [ ]: # Curva de la función de pérdida del clasificador.
plt.figure(dpi = 120)
plt.ylabel('Error cuadrático medio')
plt.xlabel('Epochs')
plt.plot(clf_iris.loss_curve_);

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 15/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Evaluamos el desempeño del clasificador sobre el conjunto de datos:


In [ ]: clf_iris.score(X_iris, y_iris)

0.7
Out[ ]:

Visualizamos la superficie de decisión:


In [ ]: plot_data(X_iris, y_iris, clf_iris)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 16/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Ahora, realizaremos el mismo proceso con los datos del dataset moons.
In [ ]: # Clasificador para el conjunto moons
clf_moons = MLPClassifier(solver='lbfgs', # Método recomendado para datasets pe
activation='tanh', # Función de activación. En este caso
max_iter=1000, # Cantidad máxima de iteraciones perm
tol=1e-4, # Tolerancia de la optimización. Si en una iterac
hidden_layer_sizes=(3, 3)) # Número de neuronas por cada ca

In [ ]: # Entrenamiento del modelo. Este proceso puede tardar.


clf_moons.fit(X_moons, y_moons)
print(clf_moons.score(X_moons, y_moons))

1.0

In [ ]: # Graficamos los resultados.


plot_data(X_moons, y_moons, clf_moons)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 17/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

MLPClassifier también nos permite acceder a la matriz de parámetros y a los sesgos w

w0 . El atributo coefs_ nos regresa una lista que corresponde a los parámetros
aprendidos. A continuación, verificamos el tamaño de cada matriz y su respectivo valor:
In [ ]: print([a.shape for a in clf_moons.coefs_])
for coefs_row in clf_moons.coefs_:
print(coefs_row)

[(2, 3), (3, 3), (3, 1)]


[[ -0.29444662 -49.06909988 6.2264449 ]
[ 0.26632199 -5.58215866 3.15374037]]
[[-23.17114353 -17.19598709 -38.9481973 ]
[ 0.45987666 -5.55041775 -6.44165375]
[ 22.44308709 -2.84976718 -31.88246622]]
[[31.08417216]
[41.48219894]
[61.6488602 ]]

Para acceder a los sesgos, se puede usar .intercepts_

In [ ]: print([a.shape for a in clf_moons.intercepts_])


for intercept in clf_moons.intercepts_:
print(intercept)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 18/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
[(3,), (3,), (1,)]
[ -0.23458911 -21.5308216 -3.18927969]
[-6.18924982 -8.63073332 2.98571116]
[-21.67943143]

3.2. Ventajas y desventajas


Ventajas
A través de las capas ocultas se pueden modelar relaciones de alto nivel entre las
entradas. Por ejemplo, en el dominio de reconocimiento de objetos en imágenes, se
puede aprender que el conjunto de dos ojos, una nariz y una boca forman la imagen de
una cara.
Las funciones de activación al ser no lineales le permiten al modelo aprender funciones
de separación más elaboradas.
Desventajas
La complejidad del modelo puede aumentar rápidamente con respecto al número de capas
y de neuronas por capa. Contemple el caso de una red con las siguientes características:
1 capa de entrada de tamaño . 50

1 capa oculta de neuronas.256

1 capa oculta de neuronas.512

1 capa de clasificación binaria.


Bajo esta configuración tenemos que el número de parámetros está distribuido de la
siguiente manera:
Conexiones entre la capa de entrada y la primera capa oculta:
256 ∗ 50 + 256 = 13056 . Donde la segunda parte corresponde a los sesgos.
Conexiones entre la primera capa oculta y la segunda capa oculta:
256 ∗ 512 + 512 = 131584 .
Conexiones entre la segunda capa oculta y la capa de clasificación: . 512 + 1 = 513

Para un total de parámetros entrenables.


145153

Recomendaciones prácticas
Hacer un escalamiento de los datos de entrada. MLPClassifier es muy sensible a la
escala de los datos de entrada.
Explorar el número de neuronas por capa y el parámetro usando GridSearchCV . α

Usar lbfgs como solver para conjuntos de datos pequeños. Mientras que adam es
más recomendado para conjuntos de datos grandes.

4. Análisis de series de tiempo


file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 19/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Las series de tiempo son una secuencia de observaciones indexadas por una variable
temporal. Una de las aplicaciones más comunes del análisis de series de tiempo es la
predicción de valores futuros utilizando datos históricos. Por ejemplo, se desea predecir el
valor de la medida en el siguiente minuto, día, mes o año basados en los datos recolectados
en los minutos, días, meses y/o años previos.

4.1 Validación cruzada para series de tiempo


Un problema de análisis de series de tiempo puede ser transformado en un problema de
aprendizaje supervisado tomando una ventana de las observaciones anteriores como los
k

datos de entrada o predictores y el valor actual como el valor objetivo o valor explicado. El
ancho de la ventana normalmente se explora como un hiperparámetro.
w

Un aspecto importante a tener en cuenta en el análisis de series de tiempo es que la


selección de los conjuntos de validación y entrenamiento para la validación cruzada o cross
validation no puede ser aleatorio como se realiza en otros problemas de machine learning
supervisado donde la secuencia y el tiempo no se toman en cuenta. En el análisis de series
de tiempo estamos interesados en predecir un valor en el futuro. De esta manera, los datos
de validación para series de tiempo siempre deben ocurrir temporalmente después de los
datos de entrenamiento. Existen dos esquemas de validación cruzada para series de tiempo
sliding window y forward chaining validation que pueden ser usados para la validación
cruzada de series de tiempo.

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 20/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

4.2. Series
artificiales de tiempo con redes neuronales
Ahora realizaremos la predicción para el dataset de temperaturas mínimas medias
semanales. Carguemos nuevamente el objeto Series.
In [ ]: # Objeto Series con el conjunto de datos.
url = 'https://drive.google.com/uc?export=download&id=1XvKsdBs6EG463iN3L1lQv9JX

mintemp = pd.read_csv(url, index_col = 0, parse_dates= True, squeeze = True)


mintemp.head()

Date
Out[ ]:
1981-01-01 20.700000
1981-01-08 16.585714
1981-01-15 19.214286
1981-01-22 18.514286
1981-01-29 16.814286
Name: Temp, dtype: float64

In [ ]: # Información general del contenido de la serie.


mintemp.describe()

count 523.000000
Out[ ]:
mean 11.197683
std 3.430824
min 2.742857
25% 8.700000
50% 11.057143
75% 13.821429
max 20.700000
Name: Temp, dtype: float64

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 21/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Vamos a visualizar la serie de los valores de temperatura. Este será el valor que
predeciremos.
In [ ]: mintemp.plot(rot=90, figsize = (12, 5), fontsize = 13.5);

Vamos a preparar el dataset para el modelado. Nuestro objetivo es entrenar un modelo


autorregresivo, en el cual el valor de la serie de tiempo en un momento dado depende Xi

de los valores anteriores.


k

La red neuronal debe tener entradas o features y salida, que corresponde al valor
k 1

actual. Las features serán las observaciones previas, que corresponden a una ventana de
k

tamaño . k

In [ ]: #@markdown **Visualización:** demostración del concepto de ventanas de tiempo.

#@markdown * **`n`**: Número de ventanas a visualizar.


n = 9#@param {type:"integer"}
#Visualización del concepto de ventana

#@markdown * **`k`**: Tamaño de la ventana.


k = 10#@param {type:"integer"}

fig, axes = plt.subplots(nrows = n//2, ncols= 2, figsize = (8, 1.5*n), dpi = 1

for i, ax in enumerate(axes.flat):
data = mintemp.iloc[i: i + k]
ax.set_title(f'Ventana {i + 1}')
ax.plot(data.index[:-1], data.values[:-1], 'r')
ax.plot(data.index[-1], data.values[-1], 'go', ms = 12)
ax.plot(data.index[:-1], data.values[:-1], 'ro')
ax.plot(data.index[-2:], data.values[-2:], 'g--')
fig.autofmt_xdate()
fig.tight_layout()

fig.legend(['Ventana', 'Valor']);

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 22/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 23/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Para generar nuestro modelo vamos a iniciar con una ventana de tiempo de 20

observaciones.
In [ ]: # Tamaño de la ventana. Puede cambiarlo si lo desea.
# Tenga en cuenta que el entrenamiento para valores mayores tardará más tiempo.

k = 20

Usaremos los registros de los primeros años del dataset (desde hasta
7 1981 1986 ) para el
conjunto de entrenamiento y validación y los siguientes (desde hasta 2 1987 1990 ) para el
conjunto de pruebas. Empezamos almacenándolos en arreglos de NumPy.
In [ ]: data_train = mintemp.loc[:'1986-12-31'] # Primeros 7 años
data_test = mintemp.loc['1987-01-01':] # Últimos 3 años.

data_train.index[-1], data_test.index[0] # Fechas de inicio de ambos conjuntos.

(Timestamp('1986-12-25 00:00:00'), Timestamp('1987-01-01 00:00:00'))


Out[ ]:

In [ ]: # Función para obtener las ventanas de tiempo.

def sliding_time(ts, window_size=1):

n = ts.shape[0] - window_size
X = np.empty((n, window_size))
y = np.empty(n)

for i in range(window_size, ts.shape[0]):


y[i - window_size] = ts[i]
X[i- window_size, 0:window_size] = np.array(ts[i - window_size:i])

return X, y

In [ ]: # Creamos las ventanas y sus valores a predecir para entrenamiento y validación

X_train, y_train = sliding_time(data_train.values, window_size=k)

In [ ]: print(f"Número de ejemplos de entrenamiento: {X_train.shape[0]} (Ventana de tam


print(f"Número de valores a predecir: {y_train.shape[0]}")

Número de ejemplos de entrenamiento: 293 (Ventana de tamaño 20)


Número de valores a predecir: 293

In [ ]: # Creamos las ventanas y sus valores a predecir para entrenamiento y validación


X_test, y_test = sliding_time(data_test.values, window_size=k)

In [ ]: print(f"Número de ejemplos de entrenamiento: {X_test.shape[0]} (Ventana de tama


print(f"Número de valores a predecir: {y_test.shape[0]}")

Número de ejemplos de entrenamiento: 190 (Ventana de tamaño 20)


Número de valores a predecir: 190
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 24/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

La fila de corresponde a una ventana de los valores anteriores a . En la siguiente


Xi X k yi

celda podemos ver el arreglo en formato de DataFrame. Note que cada fila es la fila anterior
corrida un movimiento hacia la izquierda.
In [ ]: # Observaciones de X en formato de DataFrame.
pd.DataFrame(X_train)

Out[ ]: 0 1 2 3 4 5 6 7
0 20.700000 16.585714 19.214286 18.514286 16.814286 17.571429 16.585714 19.242857
1 16.585714 19.214286 18.514286 16.814286 17.571429 16.585714 19.242857 16.142857
2 19.214286 18.514286 16.814286 17.571429 16.585714 19.242857 16.142857 17.342857
3 18.514286 16.814286 17.571429 16.585714 19.242857 16.142857 17.342857 13.828571
4 16.814286 17.571429 16.585714 19.242857 16.142857 17.342857 13.828571 12.528571
... ... ... ... ... ... ... ... ...
288 6.842857 6.300000 8.928571 5.814286 9.057143 3.171429 8.242857 8.928571
289 6.300000 8.928571 5.814286 9.057143 3.171429 8.242857 8.928571 8.928571
290 8.928571 5.814286 9.057143 3.171429 8.242857 8.928571 8.928571 8.557143
291 5.814286 9.057143 3.171429 8.242857 8.928571 8.928571 8.557143 9.157143
292 9.057143 3.171429 8.242857 8.928571 8.928571 8.557143 9.157143 8.871429
293 rows × 20 columns
En está el elemento que iría justo después del último valor de la ventana . Note que el
yi Xi

elemento es el último valor de la fila , pues en esa ventana pasa a ser el último
yi Xi+1

elemento, usado para predecir el valor . yi+1

In [ ]: # Valores a predecir y en formato de Series.


pd.Series(y_train)

0 6.300000
Out[ ]:
1 10.514286
2 11.314286
3 5.728571
4 7.400000
...
288 10.942857
289 12.957143
290 13.871429
291 11.414286
292 11.285714
Length: 293, dtype: float64

4.2.1. Partición de los datos de entrenamiento y pruebas


Como se mencionó previamente, para realizar la partición de entrenamiento y pruebas se
debe tomar en cuenta la temporalidad de la serie. Para ello utilizaremos la función
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 25/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

TimeSeriesSplit de Scikit-Learn.
In [ ]: # Selección de los datos en series de tiempo
from sklearn.model_selection import TimeSeriesSplit

# Definimos el número de splits para realizar cross-validation


tsp = TimeSeriesSplit(n_splits=3)

EL método TimeSeriesSplit toma como parámetro los arreglos X y y y genera los


índices para entrenamiento y pruebas de una validación cruzada de forward chaining igual
a la cantidad de divisiones definidas en el argumento n_splits .
In [ ]: for i, (train_index, test_index) in enumerate(tsp.split(X_train, y_train)):

print(f'-------------------- Pliegue {i + 1} --------------------')


print("\tPartición de entrenamiento")
print(f'\t\tTamaño de la partición: {train_index.shape}')
print(f'\t\tRango de valores: {train_index[0]}-{train_index[-1]}\n')

print("\tPartición de validación")
print(f'\t\tTamaño de la partición: {test_index.shape}')
print(f'\t\tRango de valores: {test_index[0]}-{test_index[-1]}\n')

-------------------- Pliegue 1 --------------------


Partición de entrenamiento
Tamaño de la partición: (74,)
Rango de valores: 0-73

Partición de validación
Tamaño de la partición: (73,)
Rango de valores: 74-146

-------------------- Pliegue 2 --------------------


Partición de entrenamiento
Tamaño de la partición: (147,)
Rango de valores: 0-146

Partición de validación
Tamaño de la partición: (73,)
Rango de valores: 147-219

-------------------- Pliegue 3 --------------------


Partición de entrenamiento
Tamaño de la partición: (220,)
Rango de valores: 0-219

Partición de validación
Tamaño de la partición: (73,)
Rango de valores: 220-292

In [ ]: #@markdown **Visualización:** demostración de la ubicación de los índices de la

n_splits = 3 #@param {type:"slider", min:3, max:7, step:1}

# La partición nos devuelve los indices de train y test.


tsp = TimeSeriesSplit(n_splits=n_splits)
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 26/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

i = 0
fig = plt.figure(figsize = (12,6), dpi = 110)
plt.set_cmap('Paired')

tsp_indexes = [(train_index, test_index) for (train_index, test_index) in tsp.s

for train_index, test_index in tsp_indexes:


plt.plot(train_index,
np.full(len(train_index), 1-i*0.001),
lw = 8,
ls= '-.',
label = f'Entrenamiento (k = {i + 1})')

plt.plot(test_index,
np.full(len(test_index), 1-i*0.001),
lw = 8,
ls= '-',
label = f'Validación (k = {i + 1})')
i+=1
fig.get_axes()[0].get_yaxis().set_visible(False)
plt.legend(ncol=1, title = 'Índices por partición', );

Visualizamos los datos de entrenamiento y validación de esta última división:


In [ ]: # Datos de prueba y entrenamiento con Matplotlib
train_index, test_index = tsp_indexes[-1]

fig = plt.figure(dpi = 120, figsize = (8, 3))


plt.plot(mintemp[train_index].index, mintemp[train_index].values, label = "Entr
plt.plot(mintemp[test_index].index, mintemp[test_index].values, label = "Prueba

plt.title('Datos de entrenamiento y pruebas (y)')


fig.autofmt_xdate()

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 27/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

4.3. Regresión
multicapa de series de tiempo con perceptrón
Ahora realizamos la regresión con una red neuronal multicapa, para predecir el valor de la
serie. En esta ocasión utilizaremos MPLRegressor . Sus parámetros son equivalentes a los
aceptados por MLPClassifier , a diferencia que el regresor permite retornar los valores
continuos generados por la función de predicción.
In [ ]: from sklearn.neural_network import MLPRegressor

model = MLPRegressor(solver = 'lbfgs',


activation = 'relu',
hidden_layer_sizes=(120, 60, 30),
max_iter=200,
n_iter_no_change=50,
validation_fraction=0.2,
random_state=1234)

In [ ]: # Entrenamos el modelo.
model.fit(X_train, y_train)

Out[ ]: ▾ MLPRegressor
MLPRegressor(hidden_layer_sizes=(120, 60, 30), n_iter_no_change=50,
random_state=1234, solver='lbfgs', validation_fraction=0.
2)

In [ ]: # Métricas de rendimiento
# Error absoluto, cuadrado, y cuadrado logarítmico.
from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_squar

y_pred = model.predict(X_test)

print(f"Test Mean Squared Error: \t{mean_squared_error(y_test, y_pred):.4f}")

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 28/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
print(f"Test Mean Absolute Error: \t{mean_absolute_error(y_test, y_pred):.4f}")
print(f"Test Mean squared log error: \t{mean_squared_log_error(y_test, y_pred):

Test Mean Squared Error: 3.6639


Test Mean Absolute Error: 1.5097
Test Mean squared log error: 0.0309

In [ ]: x = data_test.index[k:]

plt.figure(figsize=(10,3), dpi = 105)


plt.plot(x, y_test, ls = "--", label="Valor verdadero (pruebas)")
plt.plot(x, y_pred, ls = '-', label="Valor predicho (pruebas)")
plt.title("Predicción vs valores verdaderos (pruebas)")
plt.legend();

4.3.1. Validación cruzada con series de tiempo


Ahora vamos a explorar los hiperparámetros de la red para identificar sus valores más
apropiados. Para hacer esto, utilizaremos todas las particiones generadas por
TimeSeriesSplit y exploraremos manualmente las combinaciones de hiperparámetros
de MLPRegressor .
En este ejemplo exploraremos el tamaño de la capa oculta ( hidden_layer_sizes ) y la
función de activación ( activation ).
In [ ]: params = {
'hidden_layer_sizes' : [(10,), (20,), (40,), (80,) ], # Algunas arquitect
'activation' : ['logistic', 'tanh', 'relu'] # Funciones de acti
}

El objeto generado por TimeSeriesSplit puede ser usado para generar las particiones
de validación cruzada con el método GridSearchCV , pasándolo con el argumento cv .
Nota: La búsqueda explora configuraciones distintas en cada uno de los
12 5

pliegues generados con TimeSeriesSplit . Como consecuencia la función


puede tardar unos minutos en ejecutarse por completo.
In [ ]: #Grid Search para el modelo MLPRegressor
from sklearn.model_selection import GridSearchCV
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 29/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

tsp = TimeSeriesSplit(n_splits = 5)

gsearch = GridSearchCV(estimator = MLPRegressor(solver = 'lbfgs', #Modelo a ex


random_state=1234,
max_iter= 2000,
n_iter_no_change=50,
validation_fraction=0.2),
cv = tsp,
param_grid = params,
verbose = 3)

gsearch.fit(X_train, y_train)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 30/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
Fitting 5 folds for each of 12 candidates, totalling 60 fits
[CV 1/5] END activation=logistic, hidden_layer_sizes=(10,);, score=0.528 total
time= 0.7s
[CV 2/5] END activation=logistic, hidden_layer_sizes=(10,);, score=-0.316 tota
l time= 1.0s
[CV 3/5] END activation=logistic, hidden_layer_sizes=(10,);, score=-0.049 tota
l time= 1.1s
[CV 4/5] END activation=logistic, hidden_layer_sizes=(10,);, score=0.451 total
time= 1.2s
[CV 5/5] END activation=logistic, hidden_layer_sizes=(10,);, score=0.422 total
time= 0.8s
[CV 1/5] END activation=logistic, hidden_layer_sizes=(20,);, score=0.529 total
time= 0.4s
[CV 2/5] END activation=logistic, hidden_layer_sizes=(20,);, score=-0.110 tota
l time= 0.8s
[CV 3/5] END activation=logistic, hidden_layer_sizes=(20,);, score=0.244 total
time= 0.8s
[CV 4/5] END activation=logistic, hidden_layer_sizes=(20,);, score=0.164 total
time= 0.9s
[CV 5/5] END activation=logistic, hidden_layer_sizes=(20,);, score=0.333 total
time= 0.9s
[CV 1/5] END activation=logistic, hidden_layer_sizes=(40,);, score=0.387 total
time= 0.2s
[CV 2/5] END activation=logistic, hidden_layer_sizes=(40,);, score=0.114 total
time= 1.0s
[CV 3/5] END activation=logistic, hidden_layer_sizes=(40,);, score=0.260 total
time= 1.1s
[CV 4/5] END activation=logistic, hidden_layer_sizes=(40,);, score=0.132 total
time= 1.2s
[CV 5/5] END activation=logistic, hidden_layer_sizes=(40,);, score=0.262 total
time= 2.7s
[CV 1/5] END activation=logistic, hidden_layer_sizes=(80,);, score=0.265 total
time= 0.5s
[CV 2/5] END activation=logistic, hidden_layer_sizes=(80,);, score=0.194 total
time= 0.7s
[CV 3/5] END activation=logistic, hidden_layer_sizes=(80,);, score=0.224 total
time= 1.8s
[CV 4/5] END activation=logistic, hidden_layer_sizes=(80,);, score=0.431 total
time= 1.1s
[CV 5/5] END activation=logistic, hidden_layer_sizes=(80,);, score=0.008 total
time= 1.8s
[CV 1/5] END activation=tanh, hidden_layer_sizes=(10,);, score=-0.052 total ti
me= 0.7s
[CV 2/5] END activation=tanh, hidden_layer_sizes=(10,);, score=-0.414 total ti
me= 0.7s
[CV 3/5] END activation=tanh, hidden_layer_sizes=(10,);, score=0.564 total tim
e= 0.8s
[CV 4/5] END activation=tanh, hidden_layer_sizes=(10,);, score=0.677 total tim
e= 0.9s
[CV 5/5] END activation=tanh, hidden_layer_sizes=(10,);, score=0.596 total tim
e= 0.9s
[CV 1/5] END activation=tanh, hidden_layer_sizes=(20,);, score=0.550 total tim
e= 0.7s
[CV 2/5] END activation=tanh, hidden_layer_sizes=(20,);, score=0.069 total tim
e= 0.9s
[CV 3/5] END activation=tanh, hidden_layer_sizes=(20,);, score=0.488 total tim
e= 1.0s
[CV 4/5] END activation=tanh, hidden_layer_sizes=(20,);, score=0.663 total tim
e= 1.6s
[CV 5/5] END activation=tanh, hidden_layer_sizes=(20,);, score=0.372 total tim
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 31/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
e= 1.6s
[CV 1/5] END activation=tanh, hidden_layer_sizes=(40,);, score=0.507 total tim
e= 1.0s
[CV 2/5] END activation=tanh, hidden_layer_sizes=(40,);, score=0.223 total tim
e= 1.2s
[CV 3/5] END activation=tanh, hidden_layer_sizes=(40,);, score=0.050 total tim
e= 1.3s
[CV 4/5] END activation=tanh, hidden_layer_sizes=(40,);, score=0.331 total tim
e= 1.6s
[CV 5/5] END activation=tanh, hidden_layer_sizes=(40,);, score=0.053 total tim
e= 2.8s
[CV 1/5] END activation=tanh, hidden_layer_sizes=(80,);, score=0.419 total tim
e= 0.6s
[CV 2/5] END activation=tanh, hidden_layer_sizes=(80,);, score=0.368 total tim
e= 1.1s
[CV 3/5] END activation=tanh, hidden_layer_sizes=(80,);, score=0.229 total tim
e= 5.1s
[CV 4/5] END activation=tanh, hidden_layer_sizes=(80,);, score=0.244 total tim
e= 4.0s
[CV 5/5] END activation=tanh, hidden_layer_sizes=(80,);, score=0.412 total tim
e= 3.5s
[CV 1/5] END activation=relu, hidden_layer_sizes=(10,);, score=0.540 total tim
e= 0.4s
[CV 2/5] END activation=relu, hidden_layer_sizes=(10,);, score=0.547 total tim
e= 0.2s
[CV 3/5] END activation=relu, hidden_layer_sizes=(10,);, score=0.498 total tim
e= 0.5s
[CV 4/5] END activation=relu, hidden_layer_sizes=(10,);, score=0.518 total tim
e= 0.6s
[CV 5/5] END activation=relu, hidden_layer_sizes=(10,);, score=0.655 total tim
e= 0.5s
[CV 1/5] END activation=relu, hidden_layer_sizes=(20,);, score=-0.223 total ti
me= 0.9s
[CV 2/5] END activation=relu, hidden_layer_sizes=(20,);, score=0.192 total tim
e= 1.0s
[CV 3/5] END activation=relu, hidden_layer_sizes=(20,);, score=0.394 total tim
e= 0.6s
[CV 4/5] END activation=relu, hidden_layer_sizes=(20,);, score=0.541 total tim
e= 0.6s
[CV 5/5] END activation=relu, hidden_layer_sizes=(20,);, score=0.671 total tim
e= 0.5s
[CV 1/5] END activation=relu, hidden_layer_sizes=(40,);, score=0.397 total tim
e= 0.3s
[CV 2/5] END activation=relu, hidden_layer_sizes=(40,);, score=0.236 total tim
e= 0.8s
[CV 3/5] END activation=relu, hidden_layer_sizes=(40,);, score=0.055 total tim
e= 0.5s
[CV 4/5] END activation=relu, hidden_layer_sizes=(40,);, score=0.418 total tim
e= 1.0s
[CV 5/5] END activation=relu, hidden_layer_sizes=(40,);, score=0.337 total tim
e= 1.9s
[CV 1/5] END activation=relu, hidden_layer_sizes=(80,);, score=0.263 total tim
e= 0.4s
[CV 2/5] END activation=relu, hidden_layer_sizes=(80,);, score=0.271 total tim
e= 0.7s
[CV 3/5] END activation=relu, hidden_layer_sizes=(80,);, score=0.213 total tim
e= 2.3s
[CV 4/5] END activation=relu, hidden_layer_sizes=(80,);, score=0.142 total tim
e= 4.3s

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 32/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
[CV 5/5] END activation=relu, hidden_layer_sizes=(80,);, score=0.035 total tim
e= 2.7s
Out[ ]: ▸ GridSearchCV
▸ estimator: MLPRegressor

▸ MLPRegressor

Finalmente, podemos explorar el objeto generado en busca de la mejor configuración


identificada, como se realizó en el material anterior.
In [ ]: # Los mejores 10 modelos con respecto a su mean_test_score.
pd.DataFrame(gsearch.cv_results_).nlargest(10, 'mean_test_score')

Out[ ]: mean_fit_time std_fit_time mean_score_time std_score_time param_activation param_hidde


8 0.426753 0.156408 0.000982 0.000180 relu

5 1.159983 0.366401 0.000900 0.000154 tanh

7 2.855442 1.729209 0.001093 0.000175 tanh

9 0.700041 0.209623 0.000858 0.000132 relu

10 0.891606 0.556327 0.000839 0.000087 relu

4 0.789258 0.091494 0.000848 0.000132 tanh

6 1.559894 0.632008 0.000882 0.000109 tanh

1 0.765655 0.184559 0.000758 0.000066 logistic

2 1.246751 0.822594 0.000909 0.000118 logistic

3 1.194529 0.536208 0.001185 0.000129 logistic

Finalmente, para la evaluación del modelo en la tarea de predecir instancias futuras,


podemos retroalimentar los datos predichos en nuevas ventanas que se usen para predecir
datos totalmente generados por el modelo.
file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 33/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

A continuación, vamos a realizar este proceso desde la primera ventana del conjunto de
evaluación, y comparar los resultados con los valores obtenidos al predecir a partir de las
ventanas de evaluación y con los datos reales.
Las líneas generadas en la visualización tienen el siguiente significado:
Valores de entrenamiento y pruebas: La primera ventana de tiempo (en azul)
corresponde a los datos que fueron usados para entrenar y validar el desempeño del
modelo. A partir de su último valor se realizan las predicciones correspondientes.
Valores reales: Esta línea (en rojo) corresponde a los valores reales de la ventana de
tiempo final en el conjunto de datos original. Se visualiza para realizar una comparación
gráfica con los valores predichos por el modelo.
Valores predichos a partir de datos reales: Esta línea (en verde) corresponde a los
valores predichos por el modelo a partir de la ventana previa de valores reales, aunque
su final no coincida con el valor predicho con la ventana inmediatamente anterior.
Valores predichos a partir de datos predichos: Esta línea (en azul) corresponde a los
valores predichos por el modelo a partir de la ventana previa de valores construida a
partir de predicciones continuas. Al inicio se realiza una predicción con los valores
reales del final de la primera ventana de tiempo y se concatena el valor predicho al final
de la nueva ventana usada para predecir. Gracias a esta configuración se podría realizar
teóricamente una lista de predicciones sin límite.
In [ ]: #@markdown **Visualización:** Visualización de los resultados de la regresión a
#@markdown > **Nota**: La función es generada con la librería de visualización
# Últimos valores de entrenamiento a usar para la predicción.
X_last = X_test[:1]

# Listas con los datos en y, empezando desde el primer valor de pruebas.


y_last = []
y_forward = []

for i in range(len(X_test)):
# Valores predichos a partir de datos reales (X_test)
y_pred_forward = gsearch.predict(X_test[i: i + 1])
y_forward.append(y_pred_forward[0])

# Valores predichos a partir de datos predichos y retroalimentados.


y_pred_last = gsearch.predict(X_last) # Se predice el valor siguiente a part
y_last.append(y_pred_last[0]) # Guardamos el valor predicho.

# Creación de la nueva ventana añadiendo la última predicción.


X_last = np.roll(X_last, -1) # Desplazamos todos los valores hacia
X_last[0,-1] = y_pred_last # Guardamos el valor predicho en la úl

#Gráficamos las 2 predicciones distintas en comparación con los valores reales.

test_date_index = data_test.index[k:]
plot_prediction(gsearch.best_params_,
(y_test, y_forward, y_last),
test_date_index)

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 34/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales

Podemos utilizar métricas para evaluar el rendimiento obtenido con los dos métodos. El
segundo método, aun usando datos predichos únicamente, produce un resultado bastante
bueno con relación a la poca información real de la que parte para su construcción.
In [ ]: # Datos predichos a partir de datos predichos.

print(f"Test Mean Squared Error: \t{mean_squared_error(y_test, y_last):.4f}")


print(f"Test Mean Absolute Error: \t{mean_absolute_error(y_test, y_last):.4f}")
print(f"Test Mean squared log error: \t{mean_squared_log_error(y_test, y_last):

Test Mean Squared Error: 4.0843


Test Mean Absolute Error: 1.6181
Test Mean squared log error: 0.0336

In [ ]: # Datos predichos a partir de datos reales.

print(f"Test Mean Squared Error: \t{mean_squared_error(y_test, y_forward):.4f}"


print(f"Test Mean Absolute Error: \t{mean_absolute_error(y_test, y_forward):.4f
print(f"Test Mean squared log error: \t{mean_squared_log_error(y_test, y_forwar

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 35/36
7/5/23, 19:19 M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales
Test Mean Squared Error: 3.2890
Test Mean Absolute Error: 1.4498
Test Mean squared log error: 0.0270

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn en la
creación y entrenamiento de redes neuronales multicapa y el análisis y modelado de
problemas de series de tiempo, además de material de apoyo teórico para reforzar estos
conceptos:
Time Series Machine Learning Regression Framework
How (not) to use Machine Learning for time series forecasting: Avoiding the pitfalls
Analytics Vidhya - Time Series Forecasting using Python
3Blue1Brown - Neural Networks (Lista de reproducción de YouTube)
Fernando Sancho Caparrini - Redes Neuronales: una visión superficial.
Knut Hinkelmann - Neural Networks
MIT Press book - Deep learning book
course.fast.ai

Créditos
Profesor: Fabio Augusto Gonzalez
Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería

file:///Users/mac/Downloads/M2U3_Análisis_de_series_de_tiempo_con_redes_neuronales.html 36/36
7/5/23, 19:21 grupo4.ipynb - Colaboratory

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit
from ipywidgets import interact, IntSlider

Carga de Datos

time = np.linspace(0, 10, 10000)


position = np.cos(3 * time) + np.random.normal(0, 0.2, size=(10000, ))

fig, ax = plt.subplots()
ax.plot(time, position)
ax.set(xlabel="Time[s]", ylabel="Position")
ax.grid(True)

fig, ax = plt.subplots()
ax.plot(position)
ax.set(xlabel="Time [samples]", ylabel="Position")
ax.grid(True)

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 1/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

Validacion Cruzada

p = 0.6
n_train = int(position.size * p)
position_train, position_test = position[:n_train], position[n_train:]

fig, ax = plt.subplots()
ax.plot(position_train, label="Train")
ax.plot(np.arange(n_train, position.size), position_test, label="Test")
ax.set(xlabel="Time [samples]", ylabel="Position")
ax.grid(True)
ax.legend()

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 2/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

<matplotlib.legend.Legend at 0x7f66b4b28700>

Modelamiento

position_train.shape

(6000,)

window = 200
def show_window(x_0):
fig, ax = plt.subplots()
ax.plot(position_train, label="Train", alpha=0.3)
ax.plot(np.arange(n_train, position.size), position_test, label="Test", alpha=0.3)
window_idx = np.arange(x_0, x_0 + window)
position_window = position[window_idx]
ax.plot(window_idx, position_window, color="r", label="Window", alpha=0.3)
ax.scatter(x_0 + window, position[x_0 + window], color="k", s=30)
ax.set(xlabel="Time [samples]", ylabel="Position")
ax.grid(True)
ax.legend()

show_window(0)

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 3/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

interact(show_window, x_0=IntSlider(value=0, min=0, max=position_train.size - window -

x_0 0
<function __main__.show_window(x_0)>

def get_windows(serie, window_size):


inps, outs = [], []
for i in range(serie.size - window_size - 1):
inps.append(serie[i: i + window_size].reshape(1, -1))
outs.append(serie[i + window_size])
return np.concatenate(inps, axis=0), np.array(outs)

features_train, labels_train = get_windows(position_train, 200)


features_test, labels_test = get_windows(position_test, 200)

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 4/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

features_train.shape

(5799, 200)

labels_train.shape

(5799,)

features_test.shape

(3799, 200)

labels_test.shape

(3799,)

model = SVR().fit(features_train, labels_train)

model.gamma, model.C

('scale', 1.0)

model.score(features_test, labels_test)

0.9192701276983957

param_grid = {
"gamma": np.logspace(-5, -1, 5),
"C": np.logspace(-2, 2, 5),
}
param_grid

{'gamma': array([1.e-05, 1.e-04, 1.e-03, 1.e-02, 1.e-01]),


'C': array([1.e-02, 1.e-01, 1.e+00, 1.e+01, 1.e+02])}

gs = GridSearchCV(
SVR(max_iter=1000),
param_grid=param_grid,
cv=TimeSeriesSplit(n_splits=3)
).fit(features_train, labels_train)

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 5/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(
/usr/local/lib/python3.9/dist-packages/sklearn/svm/_base.py:299: ConvergenceWarni
warnings.warn(

pd.DataFrame(gs.cv_results_)

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 6/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

mean_fit_time std_fit_time mean_score_time std_score_time param_C param_g

0 0.574880 0.282892 0.560891 0.098017 0.01 0.0

1 0.568745 0.288704 0.646639 0.226588 0.01 0

2 0.688020 0.324920 0.676254 0.116375 0.01

3 0.554767 0.310189 0.511974 0.150837 0.01

4 0.602599 0.342000 0.547084 0.134025 0.01

5 0.714778 0.457047 0.728075 0.240975 0.1 0.0

6 0.587174 0.310745 0.538317 0.151286 0.1 0

7 0.562859 0.318845 0.523233 0.157349 0.1

8 0.572754 0.333940 0.531574 0.164468 0.1

9 0.726930 0.368795 0.688519 0.182481 0.1

10 0.820885 0.625653 0.718594 0.359886 1.0 0.0

11 0.557850 0.318114 0.505597 0.146480 1.0 0

12 0.736047 0.468343 0.643678 0.280989 1.0

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 7/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

gs.best_estimator_
13 0.847147 0.496754 0.713353 0.364175 1.0

▾ SVR
SVR(C=10.0,
14 gamma=0.0001,
0.772444 max_iter=1000) 0.700583
0.642939 0.289745 1.0

best_model = gs.best_estimator_
15 0.751132 0.472385 0.656458 0.242072 10.0 0.0

best_model.score(features_test, labels_test)

0.9098405847335298
16 0.564320 0.316860 0.503374 0.153626 10.0 0

y_pred = best_model.predict(features_test)

17 = plt.subplots()
fig, ax 0.510718 0.277985 0.448051 0.114756 10.0
ax.plot(labels_test, label="test")
ax.plot(y_pred, label="pred")
ax.legend()
18 0.345860 0.178115 0.301454 0.054241 10.0
<matplotlib.legend.Legend at 0x7f66b4b57130>

19 0.635779 0.409085 0.656385 0.259038 10.0

window = features_train[-1:]
window

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 8/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

array([[ 0.03306602, 0.34129955, 0.22656846, -0.09406897, -0.09295804,


-0.00563729, 0.181425 , 0.13433336, 0.04710143, -0.08640378,
0.14445577, -0.16169126, 0.49147899, 0.08398493, 0.17910979,
-0.06552552, 0.04029813, 0.1731843 , 0.23281078, 0.34273107,
0.38699626, 0.20921751, 0.44807227, 0.06834578, 0.48477954,
0.12229298, 0.25125028, -0.09339949, 0.1124352 , 0.39522278,
0.29261606, 0.38093545, 0.32147614, 0.20558935, 0.39418111,
0.25549271, 0.66471539, 0.24730673, 0.15268347, 0.30628269,
0.49180781, 0.0171622 , 0.29614332, 0.08140075, 0.52109036,
-0.1334336 , 0.28432826, 0.55313732, 0.03486905, 0.28853973,
-0.21475326, 0.17665016, 0.15982066, 0.15179645, 0.37705784,
0.46345569, 0.13627718, 0.28858962, 0.17236528, 0.34761332,
0.0982308 , 0.22160088, 0.0822213 , 0.69254379, 0.51730344,
0.38136103, 0.27589535, 0.22140086, 0.59430778, 0.21384276,
0.24749905, 0.48968481, 0.07233763, 0.14116638, 0.3206045 ,
0.25860526, 0.24518282, 0.52224086, 0.18092965, 0.34107023,
0.26786701, 0.0410382 , 0.2752143 , 0.50549443, 0.33933327,
-0.03737762, 0.21223532, 0.38913401, 0.10951445, 0.01337143,
0.26072751, 0.26480866, 0.37526514, 0.1373112 , -0.30368003,
0.23094468, 0.42275763, 0.68813424, -0.02273354, 0.43566018,
0.42237425, 0.94466143, 0.3684509 , 0.31042879, 0.07032398,
0.63949395, 0.52936188, 0.74911588, 0.48077879, 0.04983898,
0.46024951, 0.17308734, 0.27419125, 0.85128445, 0.51657156,
0.75839459, 0.11511202, 0.3348157 , 0.37475239, 0.41019909,
0.26832638, 0.02622753, 0.57442429, 0.22645975, 0.35979458,
0.91694137, 0.40842836, 0.0100276 , 0.47722829, 0.23941742,
0.62102397, 0.60256668, 0.58175622, 0.68968084, 0.43806704,
0.14562301, 0.83489015, 0.46083277, 0.26594301, 0.5608571 ,
0.26217229, 0.52115839, 0.77546431, 0.6761234 , 0.70723804,
0.45914351, 1.07451399, 0.88275517, 0.8666991 , 0.40790216,
0.64645448, 0.50523139, 0.55332035, 0.61082063, 0.72152116,
0.56469802, 0.68994298, 0.31773347, 0.56674302, 0.74219867,
0.42284126, 0.69875761, 0.68274344, 0.60017401, 0.52808439,
0.86351745, 0.7324401 , 0.80926254, 1.00675496, 0.48883142,
0.58127132, 0.4480951 , 0.88448902, 0.4483287 , 0.70435722,
0.6922428 , 0.6849323 , 0.71418805, 0.29767394, 0.82678758,
0.65380864, 1.06430245, 0.69228595, 0.56690693, 0.45836316,
0.57017524, 0.87000384, 0.96182237, 0.80326425, 0.6827019 ,
0.67699633, 0.55930922, 0.83063939, 0.61894212, 1.03567829,
0.6888774 , 0.60385243, 0.70773341, 0.6068249 , 0.78754235]])

np.roll(window, (0, -1))

array([[ 0.34129955, 0.22656846, -0.09406897, -0.09295804, -0.00563729,


0.181425 , 0.13433336, 0.04710143, -0.08640378, 0.14445577,
-0.16169126, 0.49147899, 0.08398493, 0.17910979, -0.06552552,
0.04029813, 0.1731843 , 0.23281078, 0.34273107, 0.38699626,
0.20921751, 0.44807227, 0.06834578, 0.48477954, 0.12229298,
0.25125028, -0.09339949, 0.1124352 , 0.39522278, 0.29261606,
0.38093545, 0.32147614, 0.20558935, 0.39418111, 0.25549271,
0.66471539, 0.24730673, 0.15268347, 0.30628269, 0.49180781,
0.0171622 , 0.29614332, 0.08140075, 0.52109036, -0.1334336 ,
0.28432826, 0.55313732, 0.03486905, 0.28853973, -0.21475326,
0.17665016, 0.15982066, 0.15179645, 0.37705784, 0.46345569,
0.13627718, 0.28858962, 0.17236528, 0.34761332, 0.0982308 ,
https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 9/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory
0.22160088, 0.0822213 , 0.69254379, 0.51730344, 0.38136103,
0.27589535, 0.22140086, 0.59430778, 0.21384276, 0.24749905,
0.48968481, 0.07233763, 0.14116638, 0.3206045 , 0.25860526,
0.24518282, 0.52224086, 0.18092965, 0.34107023, 0.26786701,
0.0410382 , 0.2752143 , 0.50549443, 0.33933327, -0.03737762,
0.21223532, 0.38913401, 0.10951445, 0.01337143, 0.26072751,
0.26480866, 0.37526514, 0.1373112 , -0.30368003, 0.23094468,
0.42275763, 0.68813424, -0.02273354, 0.43566018, 0.42237425,
0.94466143, 0.3684509 , 0.31042879, 0.07032398, 0.63949395,
0.52936188, 0.74911588, 0.48077879, 0.04983898, 0.46024951,
0.17308734, 0.27419125, 0.85128445, 0.51657156, 0.75839459,
0.11511202, 0.3348157 , 0.37475239, 0.41019909, 0.26832638,
0.02622753, 0.57442429, 0.22645975, 0.35979458, 0.91694137,
0.40842836, 0.0100276 , 0.47722829, 0.23941742, 0.62102397,
0.60256668, 0.58175622, 0.68968084, 0.43806704, 0.14562301,
0.83489015, 0.46083277, 0.26594301, 0.5608571 , 0.26217229,
0.52115839, 0.77546431, 0.6761234 , 0.70723804, 0.45914351,
1.07451399, 0.88275517, 0.8666991 , 0.40790216, 0.64645448,
0.50523139, 0.55332035, 0.61082063, 0.72152116, 0.56469802,
0.68994298, 0.31773347, 0.56674302, 0.74219867, 0.42284126,
0.69875761, 0.68274344, 0.60017401, 0.52808439, 0.86351745,
0.7324401 , 0.80926254, 1.00675496, 0.48883142, 0.58127132,
0.4480951 , 0.88448902, 0.4483287 , 0.70435722, 0.6922428 ,
0.6849323 , 0.71418805, 0.29767394, 0.82678758, 0.65380864,
1.06430245, 0.69228595, 0.56690693, 0.45836316, 0.57017524,
0.87000384, 0.96182237, 0.80326425, 0.6827019 , 0.67699633,
0.55930922, 0.83063939, 0.61894212, 1.03567829, 0.6888774 ,
0.60385243, 0.70773341, 0.6068249 , 0.78754235, 0.03306602]])

def forecast(window, n_steps):


predictions = []
for i in range(n_steps):
y_pred = model.predict(window)
predictions.append(y_pred)
window = np.roll(window, (0, -1))
window[0, -1] = y_pred
return np.array(predictions).flatten()

y_pred = forecast(window, 4000)

fig, ax = plt.subplots()
ax.plot(position_test, label="test")
ax.plot(y_pred, label="pred")
ax.legend()

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 10/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

<matplotlib.legend.Legend at 0x7f66b4d62f10>

y_pred = forecast(features_test[-1:], 1000)

fig, ax = plt.subplots()
ax.plot(y_pred, label="pred")
ax.legend()

<matplotlib.legend.Legend at 0x7f66b4f08a00>

!jupyter nbconvert --to html /content/grupo4_2.ipynb

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 11/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory

[NbConvertApp] Converting notebook /content/grupo4_2.ipynb to html


Traceback (most recent call last):
File "/usr/local/bin/jupyter-nbconvert", line 8, in <module>
sys.exit(main())
File "/usr/local/lib/python3.10/dist-packages/jupyter_core/application.py", lin
return super().launch_instance(argv=argv, **kwargs)
File "/usr/local/lib/python3.10/dist-packages/traitlets/config/application.py"
app.start()
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", line
self.convert_notebooks()
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", line
self.convert_single_notebook(notebook_filename)
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", line
output, resources = self.export_single_notebook(
File "/usr/local/lib/python3.10/dist-packages/nbconvert/nbconvertapp.py", line
output, resources = self.exporter.from_filename(
File "/usr/local/lib/python3.10/dist-packages/nbconvert/exporters/exporter.py"
return self.from_file(f, resources=resources, **kw)
File "/usr/local/lib/python3.10/dist-packages/nbconvert/exporters/exporter.py"
return self.from_notebook_node(
File "/usr/local/lib/python3.10/dist-packages/nbconvert/exporters/html.py", lin
return super().from_notebook_node(nb, resources, **kw)
File "/usr/local/lib/python3.10/dist-packages/nbconvert/exporters/templateexpor
output = self.template.render(nb=nb_copy, resources=resources)
File "/usr/local/lib/python3.10/dist-packages/jinja2/environment.py", line 1301
self.environment.handle_exception()
File "/usr/local/lib/python3.10/dist-packages/jinja2/environment.py", line 936
raise rewrite_traceback_stack(source=source)
File "/usr/local/share/jupyter/nbconvert/templates/lab/index.html.j2", line 3,
{% from 'jupyter_widgets.html.j2' import jupyter_widgets %}
File "/usr/local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 2, i
{% from 'celltags.j2' import celltags %}
File "/usr/local/share/jupyter/nbconvert/templates/base/display_priority.j2", l
{%- extends 'base/null.j2' -%}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 26, in t
{%- block body -%}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 29, in b
{%- block body_loop -%}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 31, in b
{%- block any_cell scoped -%}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 34, in b
{%- block codecell scoped -%}
File "/usr/local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 12,
{{ super() }}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 44, in b
{%- block output_group -%}
File "/usr/local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 38,
{{ super() }}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 48, in b
{%- block outputs scoped -%}
File "/usr/local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 44,
{{ super() }}
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 50, in b
{%- block output scoped -%}
File "/usr/local/share/jupyter/nbconvert/templates/lab/base.html.j2", line 87,
{{ super() }}
https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 12/13
7/5/23, 19:21 grupo4.ipynb - Colaboratory
File "/usr/local/share/jupyter/nbconvert/templates/base/null.j2", line 67, in b
{%- block display_data scoped -%}
i "/ / / /j / / / / j " i i

check 2 s se ejecutó 19:21

https://colab.research.google.com/drive/1ArmhPT72YC4qtgLxRIzwF1qPfSFzIniN#scrollTo=--TYGktTFiGP&printMode=true 13/13
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Aprendizaje no supervisado: agrupamiento


Los métodos discutidos hasta ahora dependen de datos etiquetados para generar sus
predicciones. El aprendizaje no supervisado es una tarea en donde se busca aprender
propiedades o patrones de un conjunto de datos sin un objetivo sobre el cuál validar si la
tarea se realizó de la manera adecuada. Estos grupos de objetos se deben crear de tal
forma en que los objetos del mismo grupo sean similares entre sí y sean diferentes a los
objetos de otros grupos.
En este material se discutirá el agrupamiento o clustering, una tarea no supervisada en
donde se busca distinguir y agrupar objetos físicos o abstractos en clases de objetos
similares. Algunos ejemplos de tareas o aplicaciones de clustering son:
Distinguir taxonomías en biología con agrupaciones por similitud biológica o incluso
genética.
Identificar páginas web similares para estructurar resultados de búsquedas
Segmentación de clientes o usuarios por un criterio de similitud definido.
El clustering es una tarea no supervisada, pues no sabemos a priori cómo clasificar
nuestros objetos, y no completamente definida, pues se plantean las preguntas:
¿Cómo cuantificamos el desempeño de un resultado de clustering?
¿Qué definición de similitud establecemos?

1. Dependencias
Importamos las librerías necesarias y definimos algunas funciones básicas de visualización
que vamos a usar en algunos ejemplos.
1.1. Dependencias
Para la construcción de modelos y ejecución de procedimientos metodológicos de
aprendizaje automático, utilizaremos la librería Scikit-learn ( sklearn ) y varias de sus
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 1/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

funciones y conjuntos de datos.


In [ ]: !pip install --upgrade matplotlib seaborn

Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w


heels/public/simple/
Requirement already satisfied: matplotlib in /usr/local/lib/python3.9/dist-pac
kages (3.7.1)
Requirement already satisfied: seaborn in /usr/local/lib/python3.9/dist-packag
es (0.12.2)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/di
st-packages (from matplotlib) (3.0.9)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.9/dist-p
ackages (from matplotlib) (0.11.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.9/di
st-packages (from matplotlib) (1.0.7)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.9/d
ist-packages (from matplotlib) (4.39.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.9/d
ist-packages (from matplotlib) (1.4.4)
Requirement already satisfied: numpy>=1.20 in /usr/local/lib/python3.9/dist-pa
ckages (from matplotlib) (1.22.4)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.9/dis
t-packages (from matplotlib) (23.0)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.9/dist-
packages (from matplotlib) (8.4.0)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.
9/dist-packages (from matplotlib) (2.8.2)
Requirement already satisfied: importlib-resources>=3.2.0 in /usr/local/lib/py
thon3.9/dist-packages (from matplotlib) (5.12.0)
Requirement already satisfied: pandas>=0.25 in /usr/local/lib/python3.9/dist-p
ackages (from seaborn) (1.4.4)
Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/dist-pa
ckages (from importlib-resources>=3.2.0->matplotlib) (3.15.0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/dist-p
ackages (from pandas>=0.25->seaborn) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/dist-packa
ges (from python-dateutil>=2.7->matplotlib) (1.16.0)

In [ ]: # Actualizamos scikit-learn a la última versión


!pip install -U scikit-learn

# Importamos scikit-learn
import sklearn

Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-w


heels/public/simple/
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-p
ackages (1.2.2)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.
9/dist-packages (from scikit-learn) (3.1.0)
Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.1.1)
Requirement already satisfied: numpy>=1.17.3 in /usr/local/lib/python3.9/dist-
packages (from scikit-learn) (1.22.4)
Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-p
ackages (from scikit-learn) (1.10.1)

Importamos además algunas librerías básicas y configuraciones de Python.


file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 2/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: # Librerías básicas NumPy, Pandas, Matplotlib y Seaborn.


import numpy as np
import pandas as pd
import matplotlib as mpl
import seaborn as sns
from matplotlib import pyplot as plt

In [ ]: # Configuraciones para las librerías y módulos usados.

# Ignoramos las advertencias o warnings.


import warnings
warnings.simplefilter(action='ignore')

# Configuramos el formato por defecto de la


# librería de visualización Matplotlib.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
mpl.rcParams['figure.dpi'] = 105
mpl.rcParams['figure.figsize'] = (9, 7)
sns.set_theme()

1.2. Funciones de utilidad y visualización


Para ilustrar los ejemplos discutidos en este material utilizaremos algunas funciones que
permiten visualizar de manera general los datos, junto a las funciones de predicción
obtenidas con cada modelo.
Nota: Matplotlib y Seaborn se encuentran por fuera del alcance de este
módulo. No es necesario que entienda estas funciones en detalle para sacar
partido del resto del contenido puesto a su disposición. Usted decide si leer o
no estas funciones en profundidad. Si decide omitir esta sección, continúe
directamente con la siguiente sección, en donde se discutirán los conjuntos
de datos que vamos a utilizar.
In [ ]: # Visualizar el resultado del agrupamiento con el algoritmo K-means para 8 valo

from google.colab import widgets

def experiment_number_of_clusters(X, clustering, show_metric=None,


plot_data=True, plot_centers=True, plot_bound

tb = widgets.TabBar([f'k = {k}'for k in range(2,10)])


for i, n_clusters in enumerate(range(2,10)):
with tb.output_to(i, select= (i < 1)):
clustering.n_clusters = n_clusters
y = clustering.fit_predict(X)

cm = 'tab10'
fig, ax = plt.subplots(figsize=(8, 6))
plot_cluster_predictions(clustering, X, n_clusters, cm,
plot_data, plot_centers, show_metric, ax = ax

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 3/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: # Visualizar el resultado del agrupamiento con el algoritmo AgglomerativeCluste

from google.colab import widgets

def experiment_hyerarchical(X, show_metric=None,


plot_data=True, plot_centers=True):

tb = widgets.TabBar([f'KN = {k}'for k in [1, 2, 3, 90]])


for i, kn in enumerate([1, 2, 3, 90]):
with tb.output_to(i, select= (i < 1)):
knn_graph = kneighbors_graph(X, kn, include_self=False)

cm = 'tab10'
fig, ax = plt.subplots(figsize=(8, 6))
plot_cluster_predictions(AgglomerativeClustering(connectivity=knn_gra
n_clusters=2, show_metric='silueta', ax = ax)

In [ ]: # Gráfica individual del resultado de un agrupamiento.


from sklearn.metrics import silhouette_score

def plot_cluster_predictions(clustering, X, n_clusters = None, cmap = 'tab10',


plot_data=True, plot_centers=True, show_metric=Non
title_str="", ax = None):

assert not hasattr(clustering, "n_clusters") or \


(hasattr(clustering, "n_clusters") and n_clusters is not None), "mus

if n_clusters is not None:


clustering.n_clusters = n_clusters

y = clustering.fit_predict(X)
# remove elements tagged as noise (cluster nb<0)
X = X[y>=0]
y = y[y>=0]

if n_clusters is None:
n_clusters = len(np.unique(y))

if ax is None:
ax = plt.gca()

if plot_data:
sns.scatterplot(x=X[:,0], y=X[:,1], hue=y, palette=cmap,
legend=False, alpha=.5, ax=ax, s=40)

if plot_centers and hasattr(clustering, "cluster_centers_"):


sns.scatterplot(x=clustering.cluster_centers_[:,0],
y=clustering.cluster_centers_[:,1], hue = np.unique(y), s=1
palette=cmap,
edgecolor="black", legend = False, ax = ax)

if show_metric is not None:


if show_metric == 'inercia' and hasattr(clustering, 'inertia_'):
inertia = clustering.inertia_
ax.set_title("Inercia = {:.0f}".format(inertia)+ title_str, fontdict=
elif show_metric == 'silueta':
sc = silhouette_score(X, y) if len(np.unique(y)) > 1 else 0
ax.set_title("Coeficiente de silueta = {:.3f}".format(sc)+ title_str,
else:
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 4/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
ax.set_title("k={}".format(n_clusters) +title_str, fontdict=dict(family

plt.axis("off")

return

In [ ]: # Gracicar la curva de aprendizaje de determinada métrica de agrupamiento.

def plot_metric(K, scores, metric_name):


plt.figure(dpi=110, figsize=(9, 5))
plt.plot(K, scores, 'bx-')
plt.xticks(K); plt.xlabel('$k$', fontdict=dict(family = 'serif', size = 14));
plt.title(f'K vs {metric_name}', fontdict=dict(family = 'serif', size = 18))
plt.show()

2. Conjuntos de datos
Para los ejemplos desarrollados en el transcurso de material, se usarán datos de Scikit-
Learn de carácter real (usando Loaders) y sintético (usando Generators).
Usaremos el dataset Iris para mostrar un ejemplo de evaluación externa, el dataset Titanic
para mostrar un ejemplo de aplicación y finalmente usaremos un conjunto de datos artificial
que cargaremos desde una URL remota.
Cargamos Iris usando el módulo sklearn.datasets .
In [ ]: from sklearn.datasets import load_iris
iris = load_iris()

print(iris.DESCR)

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 5/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
.. _iris_dataset:

Iris plants dataset


--------------------

**Data Set Characteristics:**

:Number of Instances: 150 (50 in each of three classes)


:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica

:Summary Statistics:

============== ==== ==== ======= ===== ====================


Min Max Mean SD Class Correlation
============== ==== ==== ======= ===== ====================
sepal length: 4.3 7.9 5.84 0.83 0.7826
sepal width: 2.0 4.4 3.05 0.43 -0.4194
petal length: 1.0 6.9 3.76 1.76 0.9490 (high!)
petal width: 0.1 2.5 1.20 0.76 0.9565 (high!)
============== ==== ==== ======= ===== ====================

:Missing Attribute Values: None


:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988

The famous Iris database, first used by Sir R.A. Fisher. The dataset is taken
from Fisher's paper. Note that it's the same as in R, but not as in the UCI
Machine Learning Repository, which has two wrong data points.

This is perhaps the best known database to be found in the


pattern recognition literature. Fisher's paper is a classic in the field and
is referenced frequently to this day. (See Duda & Hart, for example.) The
data set contains 3 classes of 50 instances each, where each class refers to a
type of iris plant. One class is linearly separable from the other 2; the
latter are NOT linearly separable from each other.

.. topic:: References

- Fisher, R.A. "The use of multiple measurements in taxonomic problems"


Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions to
Mathematical Statistics" (John Wiley, NY, 1950).
- Duda, R.O., & Hart, P.E. (1973) Pattern Classification and Scene Analysi
s.
(Q327.D83) John Wiley & Sons. ISBN 0-471-22361-1. See page 218.
- Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System
Structure and Classification Rule for Recognition in Partially Exposed
Environments". IEEE Transactions on Pattern Analysis and Machine
Intelligence, Vol. PAMI-2, No. 1, 67-71.
- Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule". IEEE Transaction
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 6/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
s
on Information Theory, May 1972, 431-433.
- See also: 1988 MLC Proceedings, 54-64. Cheeseman et al"s AUTOCLASS II
conceptual clustering system finds 3 classes in the data.
- Many, many more ...

Cargamos el dataset Titanic desde una Google drive remoto con gdown:
In [ ]: # Id remoto del conjunto de datos Titanic.
!gdown --id 19ciOuzzyxN-Ht03lBwHAqEyrsmWBUhDK
titanic_df = pd.read_csv('titanic.csv')

titanic_df

/usr/local/lib/python3.9/dist-packages/gdown/cli.py:121: FutureWarning: Option


`--id` was deprecated in version 4.3.1 and will be removed in 5.0. You don't n
eed to pass it anymore to use a file ID.
warnings.warn(
Downloading...
From: https://drive.google.com/uc?id=19ciOuzzyxN-Ht03lBwHAqEyrsmWBUhDK
To: /content/titanic.csv
100% 60.3k/60.3k [00:00<00:00, 10.9MB/s]

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 7/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Out[ ]: PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare
Braund,
0 1 0 3 Mr. Owen male 22.0 1 0 A/5 21171 7.2500
Harris
Cumings,
Mrs. John
1 2 1 1 Bradley female 38.0 1 0 PC 17599 71.2833
(Florence
Briggs
Th...
Heikkinen,
2 3 1 3 Miss. female 26.0 0 0 STON/O2.
3101282 7.9250
Laina
Futrelle,
Mrs.
3 4 1 1 Jacques female 35.0 1 0 113803 53.1000
Heath
(Lily May
Peel)
Allen, Mr.
4 5 0 3 William male 35.0 0 0 373450 8.0500
Henry
... ... ... ... ... ... ... ... ... ... ...
Montvila,
886 887 0 2 Rev. male 27.0 0 0 211536 13.0000
Juozas
Graham,
887 888 1 1 Miss. female 19.0 0 0 112053 30.0000
Margaret
Edith
Johnston,
Miss. W./C. 23.4500
888 889 0 3 Catherine female NaN 1 2 6607
Helen
"Carrie"
Behr, Mr.
889 890 1 1 Karl male 26.0 0 0 111369 30.0000
Howell
Dooley,
890 891 0 3 Mr. male 32.0 0 0 370376 7.7500
Patrick
891 rows × 12 columns
Retomaremos los conjuntos de datos de medias lunas y de blobs de Scikit-Learn:
In [ ]: from sklearn.datasets import make_blobs, make_moons

X_blobs, y_blobs = make_blobs(centers = 2, random_state= 321)


X_moons, y_moons = make_moons(noise = 0.1, random_state= 123)

fig, (ax1, ax2) = plt.subplots(ncols = 2, figsize = (10, 4))

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 8/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

sns.scatterplot(x = X_blobs[:, 0], y = X_blobs[:, 1], hue = y_blobs, ax = ax1);


sns.scatterplot(x = X_moons[:, 0], y = X_moons[:, 1], hue = y_moons, ax = ax2);

Cargamos el conjunto de datos artificial usando Pandas.


In [ ]: url = 'https://gist.githubusercontent.com/fagonzalezo/d4c3992ba89f7598a75adc529
cluster_df = pd.read_csv(url)
X_cluster = cluster_df.values

X_cluster.shape

(500, 2)
Out[ ]:

3. Métodos basados en centroides -


KMeans

¿Qué grupos identifica en la siguiente gráfica y cómo se puede


automatizar el proceso?
Los humanos poseen la capacidad de inferir rápidamente grupos gracias a su intuición y
poderoso sistema visual. Sin embargo, para conjuntos de datos más complejos se tienen
que plantear técnicas para que un computador pueda resolver este tipo de problemas de
manera automática.
In [ ]: url = 'https://gist.githubusercontent.com/fagonzalezo/d4c3992ba89f7598a75adc529
cluster_df = pd.read_csv(url)
X_cluster = cluster_df.values

plt.scatter(X_cluster[:,0],
X_cluster[:,1]);

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 9/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

El algoritmo k-means (o k-medias), es un método de agrupamiento que supone que los


grupos (clusters) están representadas por un prototipo o elemento representativo, que
corresponde al centroide del conjunto de datos. Es un algoritmo iterativo que en cada
iteración asigna los elementos al centroide más cercano y recalcula los centroides de
acuerdo con los nuevos elementos asignados a cada grupo.
La idea general del algoritmo se puede ver en forma de pseudo-código:
Entrada:
X: Datos a agrupar.
k: Número de clusters deseados.

Algoritmo:
1. Se seleccionan k centroides aleatoriamente.
2. Se repite hasta que los k centroides no cambien:
3. Se establecen k clusters asignando cada dato al
centroide más cercano.
4. Se recalcula el centroide de cada cluster como el
promedio (mean) de los datos.

En los siguientes videos podrá ver de manera gráfica el concepto del algoritmo aplicado:
In [ ]: #@markdown **Animación: Algoritmo *K-means***

from IPython.display import HTML

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 10/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

HTML('<iframe style="width:768px; height: 432px;" src="https://drive.google.com

Out[ ]:

In [ ]: #@markdown **Video: Ejemplo de agrupamiento con *K-means***

from IPython.display import HTML


HTML('<iframe width="758" height="432" src="https://www.youtube.com/embed/BVFG7

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 11/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Out[ ]:
K-Means Clustering Example

Podemos usar el método de Scikit-Learn sklearn.cluster.KMeans para ejecutar este


algoritmo en nuestros datos. El siguiente código aplica el algoritmo k-means al conjunto de
datos artificial. Puesto que es un modelo no supervisado, la función fit solo recibe como
argumento los datos de entrada, no las etiquetas. La función predict asigna clusters a
los ejemplos, y se usa tanto en los datos de entrenamiento como en ejemplos nuevos.
In [ ]: # Métodos de agrupamiento - Algoritmo K-means
from sklearn.cluster import KMeans

# Número de clusters que se desea generar.


n = 2

km = KMeans(n_clusters = n)
km.fit(X_cluster)

y = km.predict(X_cluster)

Usamos pandas para contar el número de elementos en cada cluster:


In [ ]: pd.Series(y).value_counts()

1 392
Out[ ]:
0 108
dtype: int64

Las coordenadas de los centroides se pueden obtener con el atributo


cluster_centers_ :

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 12/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: km.cluster_centers_

array([[ 8.01615594, 9.85357474],


Out[ ]:
[ 4.5061742 , -0.68473232]])

El siguiente código dibuja los datos agrupados junto con los centroides:
In [ ]: plt.figure(dpi = 110)
sns.scatterplot(x = X_cluster[:,0], y = X_cluster[:,1], hue = y)
plt.scatter(km.cluster_centers_[:,0], km.cluster_centers_[:,1],
marker="x", lw=3, s=200, color = 'k')

plt.legend(title = 'Clusters'); plt.xlabel("Eje x"); plt.ylabel("Eje y");

Podemos ver los parámetros usados por el modelo con .get_params() .


In [ ]: km.get_params()

{'algorithm': 'lloyd',
Out[ ]:
'copy_x': True,
'init': 'k-means++',
'max_iter': 300,
'n_clusters': 2,
'n_init': 'warn',
'random_state': None,
'tol': 0.0001,
'verbose': 0}

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 13/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

La función experiment_number_of_clusters (definida al principio del notebook)


muestra resultados con diferente número de clusters:
In [ ]: experiment_number_of_clusters(X_cluster, KMeans())

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 14/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 15/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 16/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 17/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 18/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 19/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 20/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Ahora veamos el resultado al aplicar el algoritmo en otros conjuntos de datos sintéticos


como make_blobs . Cambie cluster_std y centers en make_blobs para generar
datasets con distintas distribuciones.
¿Cuál es el número de clusters natural que usarías? ¿por qué es natural?
In [ ]: # Conjunto de datos blobs.
from sklearn.datasets import make_blobs

X,_ = make_blobs(500, cluster_std=1.5, centers=3)

experiment_number_of_clusters(X, KMeans())

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 21/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 22/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 23/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 24/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 25/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 26/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 27/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 28/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

3.1. Selección del número de clusters


Si bien para un humano es intuitivo, para conjuntos de datos más grandes, con más
dimensiones y para hacerlo de manera automática necesitamos usar otros criterios. Para
esto necesitamos usar una medida objetiva de la calidad de un clustering. Estas medidas se
encuentran definidas en el paquete sklearn.metrics.cluster . Algunas medidas son
supervisadas y otras no-supervisadas:
Medidas supervisadas: Utilizan las etiquetas reales de los ejemplos para analizar la
correspondencia entre clusters y clases.
Medidas no-supervisadas: Calculan medidas basadas en las distancias intra-cluster
y/o inter-cluster.
En este material se discutirán las medidas no-supervisadas, partiendo del hecho en que en
este tipo de tareas no siempre es posible contar con la etiqueta real de los datos.
3.1.1. Inercia o distancia intra-cluster
La distancia intra-cluster mide qué tan compacto es cada cluster y se define como:
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 29/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
n

2
∑ min(||xi − μj || )
μj ∈C
i=0

k-means minimiza esta medida, lo cual la hace una buena candidata para evaluar la calidad
de un cluster. Para esto ejecutamos k-means con diferentes valores de y graficamos el k

valor de la inercia. En esta gráfica buscamos un valor de tan pequeño como sea posible y
k

que tenga un valor de la métrica bajo. A este tipo de gráfica se le conoce usualmente como
gráfica de codo:
In [ ]: #@markdown **Video: Método del codo**

from IPython.display import HTML

HTML('<iframe style="width:768px; height: 432px;" src="https://drive.google.com

Out[ ]:

Para ilustrar este concepto, vamos a generar un modelo de agrupamiento para cada valor
de entre y . Al final de la generación, obtenemos el valor de la métrica de inercia con
k 2 15

el atributo intertia_ .
In [ ]: X,_ = make_blobs(500, cluster_std=1.5, centers=6, random_state=10)

sum_of_squared_distances = []
K = range(2,15)
for k in K:
km = KMeans(n_clusters=k)
km = km.fit(X)
sum_of_squared_distances.append(km.inertia_)

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 30/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

plot_metric(K, sum_of_squared_distances, 'Inercia')

In [ ]: experiment_number_of_clusters(X, KMeans(), show_metric='inercia')

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 31/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 32/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 33/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 34/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 35/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 36/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 37/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 38/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Ahora aplicamos la misma estrategia para el conjunto de datos artificial:


In [ ]: sum_of_squared_distances = []
K = range(2,15)
for k in K:
km = KMeans(n_clusters=k)
km = km.fit(X_cluster)
sum_of_squared_distances.append(km.inertia_)

plot_metric(K, sum_of_squared_distances, 'Inercia')

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 39/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: experiment_number_of_clusters(X_cluster, KMeans(), show_metric='inercia')

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 40/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 41/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 42/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 43/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 44/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 45/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 46/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 47/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

La gráfica de codo nos sugiere un valor de de 5 o 6. La razón es que hay un cluster mucho
k

más grande que el otro. Esto evidencia algunos de los problemas que tiene la inercia o suma
de distancia intra-cluster:
La inercia supone que los clusters son convexos e isotrópicos, lo que no siempre es así.
Responde mal a los grupos alargados, o múltiples con formas irregulares.
La inercia supone que los clusters son de tamaños similares, pues penaliza mucho más
fuertemente clusters grandes.
La inercia no es una métrica normalizada: solo sabemos que los valores más bajos son
mejores y que el cero es el óptimo.
3.1.2. Coeficiente de silueta
El coeficiente de silueta combina la distancia media intra-cluster ( ) y la distancia media al
a

grupo más cercano ( ) para cada muestra ( ):


b si

b − a
si =
max(a, b)

Es una medida que está entre y para cada muestra. Un valor cercano a indica que la
−1 1 1

distancia inter-cluster es mucho más grande que la distancia intra-cluster. También indica
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 48/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

que la muestra que estamos evaluando está en la frontera entre dos clusters.
Nota: Tenga en cuenta que el coeficiente de silueta solo tiene sentido cuando
2 <= k <= n con siendo el número de clusters y el tamaño de la
k n

muestra.
Para calcular el coeficiente de silueta del proceso de agrupamiento sklearn utiliza la media
de cada valor de silueta si

In [ ]: # Métricas de rendimiento
from sklearn.metrics import silhouette_score

X,_ = make_blobs(500, cluster_std=1.5, centers=6, random_state=10)


silhouette_scores = []

K = range(2,15)
for k in K:
km = KMeans(n_clusters=k)
km = km.fit(X)
y = km.predict(X)
silhouette_scores.append(silhouette_score(X, y))

plot_metric(K, silhouette_scores, 'Coeficiente de silueta')

En contraste con el diagrama de inercia donde buscamos el codo de la gráfica, aquí


buscamos el valor máximo. Que en este caso se obtiene en . Para el segundo
k = 5

conjunto de datos tenemos:


In [ ]: X = X_cluster
silhouette_scores = []
K = range(2,15)

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 49/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
for k in K:
km = KMeans(n_clusters=k)
km = km.fit(X)
y = km.predict(X)
silhouette_scores.append(silhouette_score(X, y))

plot_metric(K, silhouette_scores, 'Coeficiente de silueta')

En este caso el coeficiente de silueta máximo se alcanza en k = 2 , que se ajusta más a lo


esperado intuitivamente.
3.1.3. Evaluación externa
En la evaluación externa se usan datos adicionales que no estaban disponibles durante el
entrenamiento al algoritmo de agrupamiento. Por ejemplo, las categorías reales de los
ejemplos. Este tipo de evaluación también se conoce como evaluación supervisada.
En este caso usaremos Iris y evaluaremos el desempeño de K-means sobre Iris para varios
valores de con varias métricas externas.
k

In [ ]: # Partición en pruebas y entrenamiento.


from sklearn.model_selection import train_test_split

iris = load_iris()
X_iris, y_iris = iris.data, iris.target

Definimos la función plot_scores para graficar una métrica supervisada para varios
valores de . k

In [ ]: def plot_extern_metric(X, y, metric, metric_name):


scores = []
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 50/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
for i in range(2,20):
model = KMeans(n_clusters=i, random_state=32)
model.fit(X)
y_pred = model.predict(X)
scores.append(metric(y, y_pred))

plot_metric(range(2, 20), scores, metric_name)

3.1.3.1. Homogeneidad
Para calcular la homogeneidad del proceso de agrupamiento, cada grupo es asociado con la
clase mayoritaria; luego, La métrica se evalúa contando el número de ejemplos clasificados
correctamente y dividiendo por N (Manning, Raghavan & Schütze, 2008).
En este caso está acotada entre y . Un valor cercano a nos indicará que las muestras
0 1 1

de un cluster dado pertenecen en su mayoría a una sola clase.


Scikit-learn implementa el método homogeneity_score en el paquete metrics .
Después de importar la métrica, la usamos con la función plot_scores .
In [ ]: from sklearn.metrics import homogeneity_score

In [ ]: plot_extern_metric(X_iris, y_iris, homogeneity_score, 'Homogeneidad')

Podemos ver que se tendría que aplicar un análisis como el método del codo para
determinar el número óptimo de clusters. En este caso se puede observar que luego de 8

clusters la medida de homogeneidad llega a un punto de codo invertido. En este caso


valores de entre y serían correctos.
k 7 8

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 51/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Debemos tener en cuenta que la homogeneidad no es una medida simétrica, y si


cambiamos y_true con y_pred , la función dará el puntaje de completitud de los
clusters.
3.1.3.2. Información mutua
La información mutua es una medida de la similitud entre dos etiquetas de los mismos
datos. Es una métrica simétrica, si se cambian las posiciones de y_true y y_pred la
métrica retorna el mismo valor. La información mutua tiene una importante interpretación en
la teoría de la información. En términos de teoría de la información, esta medida nos da qué
tanta información ganamos de una variable no observada al observar una variable
relacionada.
Scikit-Learn implementa mutual_info_score en el paquete metrics .
Después de importar la métrica la usamos con la función plot_scores .
In [ ]: from sklearn.metrics import mutual_info_score

In [ ]: plot_extern_metric(X_iris, y_iris,
mutual_info_score, 'Información mutua')

Podemos ver que se tendría que aplicar un análisis como el método del codo para
determinar el número óptimo de clusters. En este caso podemos observar que el valor de
cambio es . En este caso la medida nos dice que al tener clusters, obtenemos casí el
7 7

máximo información posible.


3.1.3.3. Índice de Rand
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 52/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

El índice de Rand computa una medida de similitud entre dos agrupamientos al considerar
todas las parejas de ejemplos, contando el número de ejemplos que pertenecen al mismo
grupo y los que no en el agrupamiento real y en el predicho.
Scikit-learn implementa adjusted_rand_score en el paquete metrics . Esta
implementación es una versión corregida del índice de Rand que tiene en cuenta el
"chance", es decir la aleatoriedad de los agrupamientos. Agrupamientos aleatorios tienen un
índice de Rand ajustado cercanos a , con siendo un agrupamiento perfecto.
0 1

Después de importar la métrica la usamos con la función plot_scores .


In [ ]: from sklearn.metrics import adjusted_rand_score

plot_extern_metric(X_iris, y_iris,
adjusted_rand_score, 'Índice de Rand ajustado')

Podemos ver que el índice de Rand es una métrica que buscamos máximizar, en este caso
escogeríamos .
k = 3

3.1.3.4. Matriz de Contingencia


Una matriz de contingencia es una matriz similar a la matriz de confusión que muestra la
relación entre las etiquetas asignadas para cada grupo y las etiquetas reales.
Scikit-learn implementa contigency_matrix en el paquete metrics.cluster .
Definimos la función show_contigency_matrix para entrenar un modelo KMeans ,
calcular la matriz de contigencia y retornarla como un objeto DataFrame de Pandas, lo
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 53/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

que facilita su visualización.


In [ ]: from sklearn.metrics.cluster import contingency_matrix

def show_contigency_matrix(X, y, n_clusters, classes):


# Fijamos la semilla aleatoria para obtener resultados reproducibles.
model = KMeans(n_clusters, random_state=32)
model.fit(X)
y_pred = model.predict(X)
mat = contingency_matrix(y, y_pred)
columns = ['Cluster ' + str(i) for i in range(n_clusters)]

# Se retorna cómo un DataFrame de Pandas para mejorar la visualización.


return pd.DataFrame(mat, columns=columns, index=classes)

Como hemos visto (y también de manera intuitiva), un valor de bueno es 3 o 4. k

Ejecute las siguientes celdas para ver la matriz de contingencia para y . k = 3 k = 4

In [ ]: show_contigency_matrix(X_iris, y_iris, 3, iris.target_names)

Out[ ]: Cluster 0 Cluster 1 Cluster 2


setosa 50 0 0
versicolor 0 2 48
virginica 0 36 14

Podemos ver que el grupo corresponde a la especie setosa (clase ), el grupo


0 0 1

corresponde mayormente a la especie virginica (clase ), y el grupo corresponde a la


2 2

especie versicolor principalmente (clase 1).


El orden de los grupos y las clases no corresponden, esto tiene sentido, pues KMeans no
tuvo acceso a las etiquetas durante el entrenamiento.
In [ ]: show_contigency_matrix(X_iris, y_iris, 4, iris.target_names)

Out[ ]: Cluster 0 Cluster 1 Cluster 2 Cluster 3


setosa 0 50 0 0
versicolor 23 0 0 27
virginica 17 0 32 1

Si lo intentamos con un total de grupos, podemos ver que el grupo y el grupo


4 0 3

corresponden a versicolor (clase ), el grupo corresponde mayormente a la especie


1 1

setosa y el grupo corresponde principalmente a la especie virginica.


2

4.conectividad
Métodos jerárquicos
- basados en
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 54/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

AgglomerativeClustering

Para los dos dataset anteriores los resultados obtenidos son apropiados, aunque requieren
de un buen uso de las métricas para obtener el número de clusters que se deben generar.
k

Ahora, apliquemos el algoritmo en el dataset sintético de medias lunas con la función


make_moons .

In [ ]: from sklearn.datasets import make_moons


X,_ = make_moons(500, noise=.1)

plot_cluster_predictions(KMeans(), X, n_clusters=2, cmap='seismic', show_metric

Los grupos en este dataset no son convexos, por lo que k-means no es el mejor
acercamiento al problema. Cuando los clusters no son globulares, otros métodos pueden
producir mejores resultados.
Los métodos de agrupamiento basados en conectividad utilizan relaciones de vecindad
entre los elementos para encontrar grupos. Estos métodos requieren construir una matriz
de conectividad de los puntos. La función kneighbors_graph del paquete neighbors
construye una matriz basada en los vecinos más cercanos de cada punto.
k

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 55/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: X, _ = make_moons(500, noise=.1)

Con la siguiente celda puede visualizar los K vecinos más cercanos de un punto
seleccionado al azar.
In [ ]: from sklearn.neighbors import kneighbors_graph

# Obtenemos el grafo de k- vecinos más cercanos por punto.


knn_graph = kneighbors_graph(X, 20, include_self=False)

# Obtenemos los n (20) vecinos más cercanos de un punto al azar


i = np.random.randint(len(X))
nn = X[knn_graph[i].toarray()[0].astype(bool)]

# Graficamos (en azul) 20 puntos más cercanos.


plt.scatter(nn[:,0], nn[:,1], color="darkblue", alpha=1)

# Graficamos el resto de punto con menos opacidad.


plt.scatter(X[:,0], X[:,1], color="blue", alpha=.2)

# Graficamos (en rojo) el punto sobre el cual se evalúa la cercanía.


plt.scatter(X[i,0], X[i,1], s=150, color="red")

plt.xlim(np.min(X[:,0])-.1, np.max(X[:,0])+.1); plt.ylim(np.min(X[:,1])-.1, np.

Usamos esta matriz de conectividad para suministrar información de estructura al


algoritmo. En este caso vamos a usar el algoritmo de clustering aglomerativo
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 56/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

AgglomerativeClustering . Este recibe como argumento el grafo de conectividad


( connectivity ) y el criterio de enlazamiento entre grupos ( linkage ).
In [ ]: from sklearn.cluster import AgglomerativeClustering

X,_ = make_moons(500, noise=.05)


knn_graph = kneighbors_graph(X, 20, include_self=False)

# Declaramos el modelo
ac = AgglomerativeClustering(connectivity=knn_graph, linkage="average")

plot_cluster_predictions(ac, X, n_clusters=2)

Cómo podemos ver, el método jerárquico se desempeña mucho mejor con las media lunas.
El algoritmo AgglomerativeClustering realiza un agrupamiento jerárquico de la
siguiente manera:
Cada ejemplo empieza en su propio grupo, y los grupos son fusionados iterativamente.
Los dos grupos que son fusionados en cada iteración dependen del criterio de
enlazamiento, el cuál es definido por el parámetro linkage .
En este caso usamos el criterio de enlazamiento promedio ( linkage = "average" ).
Este criterio minimiza el promedio de la distancia entre todos los ejemplos de cada par de
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 57/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

grupos.
A continuación, podemos ver cómo los grupos encontrados por el algoritmo varía
dependiendo del tamaño de la vecindad.
In [ ]: experiment_hyerarchical(X, show_metric='silueta')

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 58/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 59/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 60/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

5. Ejemplo de aplicación: Titanic


El 15 de abril de 1912, durante su viaje inaugural, el ampliamente considerado
"insumergible" RMS Titanic se hundió después de chocar con un iceberg.
Desafortunadamente, no había suficientes botes salvavidas para todos a bordo, resultando
en la muerte de 1502 de 2224 pasajeros y tripulación.
Aunque había un elemento de suerte en la supervivencia, parece que algunos grupos de
personas tenían más probabilidades de sobrevivir que otros.
Para finalizar este notebook se mostrará un ejemplo con el conjunto de datos Titanic,
disponible en Kaggle.
Utilizando Scikit-learn veremos un ejemplo del análisis de grupos y la supervivencia de
estos. Este ejemplo incluye:
Preprocesamiento ( OneHotEncoder , StandardScaler y ColumnTransformer ) .
Agrupamiento con KMeans .
Evaluación del desempeño externa e interna.
Interpretación de los centroides.
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 61/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

5.1. Cargar el conjunto de datos


Usando un Id público de google drive para cargar el conjunto de datos:
In [ ]: # Id remota del conjunto de datos Titanic.
!gdown --id 19ciOuzzyxN-Ht03lBwHAqEyrsmWBUhDK
titanic_df_raw = pd.read_csv('titanic.csv')

/usr/local/lib/python3.9/dist-packages/gdown/cli.py:121: FutureWarning: Option


`--id` was deprecated in version 4.3.1 and will be removed in 5.0. You don't n
eed to pass it anymore to use a file ID.
warnings.warn(
Downloading...
From: https://drive.google.com/uc?id=19ciOuzzyxN-Ht03lBwHAqEyrsmWBUhDK
To: /content/titanic.csv
100% 60.3k/60.3k [00:00<00:00, 36.4MB/s]

El conjunto de Titanic cuenta con las siguientes características:


Variable Definición Valores
survival Supervivencia 0 = No, 1 = Sí
pclass Clase del tiquete 1 = 1ra, 2 = 2da, 3 = 3ra
sex Sexo
Age Edad en años
sibsp # de hermanos / cónyuge abordo del
Titanic
parch # de padres / hijos abordo del Titanic
ticket Número del ticket
fare Costo del ticket
cabin Número de la cabina
embarked Puerto de embarque C = Cherbourg, Q = Queenstown, S =
Southampton
Veamos datos básicos del conjunto de datos con ayuda de info :
In [ ]: titanic_df_raw.info()

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 62/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

No utilizaremos todas las características de Titanic, y nos limitaremos al subconjunto de


características:
['Embarked', 'Sex', 'Pclass', 'SibSp', 'Parch', 'Fare', 'Survived',
'Age']
Excluimos la característica Cabin por simplicidad, pues esta cuenta con muchos valores
faltantes.
In [ ]: features = ['Embarked', 'Sex', 'Pclass', 'SibSp', 'Parch', 'Fare', 'Survived',
titanic_df = titanic_df_raw[features].dropna(axis=0)

# Visualizamos nuevamente la información del conjunto de datos resultante:


titanic_df.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 712 entries, 0 to 890
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Embarked 712 non-null object
1 Sex 712 non-null object
2 Pclass 712 non-null int64
3 SibSp 712 non-null int64
4 Parch 712 non-null int64
5 Fare 712 non-null float64
6 Survived 712 non-null int64
7 Age 712 non-null float64
dtypes: float64(2), int64(4), object(2)
memory usage: 50.1+ KB

Como podemos ver, Embarked y Sex son variables categóricas y el resto son numéricas.
Esta información es necesaria para entender el ejemplo.
Antes de proceder con el preprocesamiento, convertimos los datos a la forma X, y .
Tenga en cuenta que en muchos casos no se cuenta con etiquetas en los ejercicios de
agrupamiento. Es decir, no se cuenta con un X y con un y . Sin embargo, en este ejemplo
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 63/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

usaremos la variable 'Survived' para evaluar el desempeño de los agrupamientos de


manera externa.
Por esta razón no usaremos la variable Survived durante el entrenamiento, pero si en la
evaluación.
In [ ]: X_titanic = titanic_df.drop(['Survived'], axis=1)
y_titanic = titanic_df.Survived

print(f'El shape de X_titanic es: {X_titanic.shape}')


print(f'El shape de y_titanic es: {y_titanic.shape}')

El shape de X_titanic es: (712, 7)


El shape de y_titanic es: (712,)

Observe que está vez no hemos convertido el DataFrame a un arreglo de Numpy, esto nos
facilitará el preprocesamiento más adelante.
La principal ventaja de esto es que nos permite usar los nombres de las características. De
igual manera, cómo veremos, no tendremos que preocuparnos por concatenar los datos
transformados (unos categóricos y otros numéricos).

5.2. Preprocesamiento
Para este ejemplo usaremos dos transformaciones conocidas: OneHotEncoder (para las
características categóricas) y StandardScaler (para las características numéricas).
Con el fin de simplificar el código usaremos ColumnTransformer . El objeto
ColumnTransformer es una utilidad que permite aplicar distintas transformaciones a
distintas columnas de un DataFrame de Pandas.
Primero, importamos las clases que necesitamos:
In [ ]: from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer

Definimos dos listas, una con los nombres de las características categóricas y otra con las
características numéricas.
In [ ]: categoric = ['Embarked', 'Sex']
numeric = ['Pclass', 'SibSp', 'Parch', 'Fare', 'Age']

Definimos nuestro ColumnTransformer :


Podemos ver que recibe una lista de tuplas, cada tupla define una transformación a un
conjunto de características (columnas).
Las tuplas tienen la forma:
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 64/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

('nombre', Transformación(), [característica_1,


característica_2, ...])

Donde el primer valor asigna el nombre de la transformación (lo usaremos más adelante), el
segundo valor de la tupla define el tipo de transformación con un objeto de sklearn y el
tercer valor corresponde a una lista con los nombres de las columnas a transformar.
En nuestro caso, definimos dos transformaciones:
onehot : One Hot Encoding para las características categóricas.
scaler : Estandarización para las características numéricas.

La intuición detrás de estandarizar las variables numéricas para KMeans proviene de que
KMeans por defecto utiliza una métrica de distancia euclidiana, la cual es sensible a las
unidades de las características. Es decir, una característica con alta varianza podría influir
más en los resultados del agrupamiento. Con la estandarización nos aseguramos de que
cada característica influye a la distancia de una manera similar.
Cómo regla general, debería probar el desempeño de un agrupamiento sin
preprocesamiento y compararlo con el desempeño de un agrupamiento con
preprocesamiento.
In [ ]: tf = ColumnTransformer([('onehot', OneHotEncoder(), categoric),
('scaler', StandardScaler(), numeric)])

Finalmente, aplicamos las transformaciones a el conjunto de datos:


In [ ]: X_preprocessed = tf.fit_transform(X_titanic)

print(f'El shape de X_titanic es: {X_titanic.shape}')


print(f'El shape de X_preprocessed es: {X_preprocessed.shape}')

El shape de X_titanic es: (712, 7)


El shape de X_preprocessed es: (712, 10)

Podemos ver que pasamos de características a . Esto se debe a que la característica


7 10

Embarked cuenta con valores únicos y Sex con dos, es decir pasamos de
3 1

característica para Embarked a (suma ) y de característica para Sex a (suma ).


3 2 1 2 1

Podemos verificarlo con la función get_feature_names() de onehot (nuestro


OneHotEncoder ). Para acceder a onehot usamos el atributo named_transformers_
de tf (nuestro ColumnTransformer ):
In [ ]: onehot_categories = tf.named_transformers_['onehot'].get_feature_names_out()

onehot_categories

array(['Embarked_C', 'Embarked_Q', 'Embarked_S', 'Sex_female', 'Sex_male'],


Out[ ]:
dtype=object)

De esta manera, podemos interpretar un ejemplo del conjunto de datos transformado:


file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 65/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

In [ ]: X_preprocessed[0]

array([ 0. , 0. , 1. , 0. , 1. ,
Out[ ]:
0.90859974, 0.52251079, -0.50678737, -0.51637992, -0.52766856])

Los primeros valores corresponden a las variables del One Hot Encoding de Embarked
3 3

y las siguientes corresponden a el One Hot Encoding de Sex .


2

Las siguientes variables corresponden a la estandarización de las variables numéricas (en


5

el orden de la lista numeric ).


In [ ]: # Datos originales
titanic_df.loc[:5, numeric]

Out[ ]: Pclass SibSp Parch Fare Age


0 3 1 0 7.2500 22.0
1 1 1 0 71.2833 38.0
2 3 0 0 7.9250 26.0
3 1 1 0 53.1000 35.0
4 3 0 0 8.0500 35.0

In [ ]: tf.named_transformers_['scaler']
# Datos originales
X_preprocessed[:5, 5:]

array([[ 0.90859974, 0.52251079, -0.50678737, -0.51637992, -0.52766856],


Out[ ]:
[-1.48298257, 0.52251079, -0.50678737, 0.69404605, 0.57709388],
[ 0.90859974, -0.55271372, -0.50678737, -0.50362035, -0.25147795],
[-1.48298257, 0.52251079, -0.50678737, 0.35032585, 0.36995092],
[ 0.90859974, -0.55271372, -0.50678737, -0.50125747, 0.36995092]])

5.3. Evaluación del desempeño


Procedemos a realizar la evaluación del desempeño interna y después la evaluación del
desempeño externa.
5.3.1. Evaluación del desempeño interna (no supervisada)
Con la siguiente celda podemos ver la curva de inercia y coeficiente de silueta:
In [ ]: inertia = []
silhouette = []
K = range(2, 15)
for i in K:
# Declaramos y ejecutamos el algoritmo K-means.
model = KMeans(n_clusters=i)
model.fit(X_preprocessed)

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 66/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
# Predecimos las etiquetas de X_preprocessed.
y = model.predict(X_preprocessed)

# Almacenamos la métrica de inercia y el coeficiente de silueta.


inertia.append(model.inertia_)
silhouette.append(silhouette_score(X_preprocessed, y))

plot_metric(K, inertia, 'Inercia')


plot_metric(K, silhouette, 'Coeficiente de silueta')

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 67/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

A partir de la evaluación del desempeño interno, podríamos concluir que un buen número
para es 3, el valor que maximiza el coeficiente de silueta.
k

5.3.2. Evaluación del desempeño externa (supervisada)


Graficamos la homogeneidad:
In [ ]: plot_extern_metric(X_preprocessed, y_titanic, homogeneity_score, 'Homogeneidad'

Graficamos la información mutua:


In [ ]: plot_extern_metric(X_preprocessed, y_titanic, mutual_info_score, 'Información m

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 68/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Graficamos el índice de Rand:


In [ ]: plot_extern_metric(X_preprocessed, y_titanic, adjusted_rand_score, 'Índice de R

A diferencia de la evaluación del desempeño interna, la externa nos sugiere usar un valor de
k más grande.
En este caso podríamos concluir que un buen valor para es , el valor que maximiza el
k 6

índice ajustado de Rand. Recuerde que el índice de Rand es una medida de similitud entre
los dos agrupamientos.
file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 69/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Cabe aclarar que, la selección de depende del objetivo de análisis, si este tiene que ver
k

con la supervivencia es mejor quedarse con sugerido por los métodos de evaluación
k = 6

externa.

5.4. Interpretación de los centroides


Para la interpretación de los centroides obtenidos en el proceso vamos a examinar los
valores de los centroides encontrados por KMeans para conocer el prototipo de cada
grupo. Si tomáramos el atributo cluster_centers sin más obtendríamos datos
numéricos estandarizados, los cuales serían díficiles de interpretar.
Para esto, definimos las funciones show_survival_ratios , que imprime la proporción
de personas que sobrevivieron de cada grupo encontrado por KMeans y la función
show_centroids que aplica la transformación inversa a la estandarización
( scaler.inverse_transform ) a las características numéricas de cada centroide,
recolecta cada centroide en una lista y los retorna cómo un DataFrame con las columnas
correspondientes para facilitar la lectura.
No se aplica una transformación inversa al One Hot Encoding, pues no estaría bien definido
si los valores son distintos de y . Los valores de un centroide del One Hot Encoding se
0 1

pueden interpretar como porcentajes de presencia de cada valor en un grupo.


En cada función se redondean los valores a decimales para facilitar la lectura. Lea las
5

implementaciónes con detenimiento:


In [ ]: def show_survival_ratios(X, y, k):
# Se entrena KMeans con k grupos y random_state fijo para obtener resultados
model = KMeans(n_clusters=k, random_state=32)
model.fit(X_preprocessed)

# Obtenemos las etiquetas de grupo del modelo.


y_pred = model.predict(X)

for i in range(k):
# Ejemplos que pertenecen al i-esimo grupo.
ids_group = y_pred == i

# Etiquetas reales de los ejemplos del grupo.


labels_group = y[ids_group]

# Calculamos la proporción como la media, pues consiste en valores con 0 y


survival_ratio = np.round(np.mean(labels_group), 5)
print(f'Proporción de supervivencia grupo {i}: {survival_ratio}')

In [ ]: def show_centroids(X, k, tf):


# Se entrena KMeans con k grupos y random_state fijo para obtener resultados
model = KMeans(n_clusters=k, random_state=32)
model.fit(X_preprocessed)

# Guardamos scaler para usar la transformación inversa después.


file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 70/73
11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento
scaler = tf.named_transformers_['scaler']

# Inicializamos una lista de los centroides vacía.


centroids = []

# Iteramos por cada centroide (con estandarización)

for centroid in model.cluster_centers_:


# Creamos una copia del centroide, en otro caso podríamos sobrescribir los
centroid_copy = centroid.copy()
# Se aplica la transformación inversa a las características numéricas.
centroid_copy[5:] = scaler.inverse_transform([centroid_copy[5:]])
# Redondeamos y almacenamos el centroide.
centroids.append(np.round(centroid_copy, 5))

# Definimos una lista con los nombres de las columnas correspondientes.


columns = ['C', 'Q', 'S', 'female', 'male', 'Pclass', 'SibSp', 'Parch', 'Fare

# retornamos un DataFrame
return pd.DataFrame(centroids, columns=columns)

Debido a los resultados de la evaluación del desempeño externa e interna, que indican que
3 y son valores apropiados para , mostramos los centroides para estas dos
6 k

configuraciones:
Visualizamos la matriz de contingencia junto con las funciones que se acaban de definir
para :
k = 3

In [ ]: show_contigency_matrix(X_preprocessed, y_titanic, 3, ['Survived: False', 'Survi

Out[ ]: Cluster 0 Cluster 1 Cluster 2


Survived: False 49 302 73
Survived: True 57 113 118

In [ ]: show_survival_ratios(X_preprocessed, y_titanic, 3)
show_centroids(X_preprocessed, 3, tf)

Proporción de supervivencia grupo 0: 0.53774


Proporción de supervivencia grupo 1: 0.27229
Proporción de supervivencia grupo 2: 0.6178
Out[ ]: C Q S female male Pclass SibSp Parch Fare Age
0 0.11321 0.04717 0.83962 0.57547 0.42453 2.50000 1.83962 1.89623 41.42146 14.50708
1 0.10602 0.04819 0.84578 0.27470 0.72530 2.70361 0.22410 0.10602 12.66353 28.45041
2 0.38743 0.01571 0.59686 0.43979 0.56021 1.08901 0.40838 0.32984 78.35519 40.63089

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 71/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Podemos darnos cuenta de que los centroides muestran el valor medio de cada
característica para las personas a el grupo.
En el caso de las variables categóricas, los valores del centroide corresponden a las
proporciones de personas con cada uno de los valores posibles.
Una observación que se puede realizar es sobre el grupo , este tiene la menor proporción
1

de supervivencia ( ) y podemos observar que la proporción de puerto de


0.27059

embarcamiento ( Embarked ) para el valor S (Southampton) corresponde al . 0.84471

Es decir, una gran proporción de personas que embarcaron en Southampton no


sobrevivieron.
Al igual que con grupos, visualizamos la matriz de contingencia junto con las funciones
3

para k = 6:
In [ ]: show_contigency_matrix(X_preprocessed, y_titanic, 6, ['Survived: False', 'Survi

Out[ ]: Cluster 0 Cluster 1 Cluster 2 Cluster 3 Cluster 4 Cluster 5


Survived: False 7 250 28 41 18 80
Survived: True 25 41 10 90 29 93

In [ ]: show_survival_ratios(X_preprocessed, y_titanic, 6)
show_centroids(X_preprocessed, 6, tf)

Proporción de supervivencia grupo 0: 0.78125


Proporción de supervivencia grupo 1: 0.14089
Proporción de supervivencia grupo 2: 0.26316
Proporción de supervivencia grupo 3: 0.68702
Proporción de supervivencia grupo 4: 0.61702
Proporción de supervivencia grupo 5: 0.53757
Out[ ]: C Q S female male Pclass SibSp Parch Fare A
0 0.40625 0.00000 0.59375 0.65625 0.34375 1.00000 0.75000 1.34375 222.03464 29.278
1 0.09966 0.03436 0.86598 0.00344 0.99656 2.78351 0.19588 0.06529 11.87707 28.339
2 0.07895 0.10526 0.81579 0.47368 0.52632 2.92105 3.57895 1.44737 31.30855 7.881
3 0.11450 0.07634 0.80916 0.93893 0.06107 2.49618 0.43511 0.31298 16.04765 23.396
4 0.17021 0.02128 0.80851 0.72340 0.27660 2.40426 0.48936 2.63830 31.83670 25.017
5 0.35838 0.01734 0.62428 0.35838 0.64162 1.16763 0.39884 0.15029 53.53904 42.664

Podemos observar, por ejemplo, el grupo . Es el grupo con mayor proporción de


0

supervivencia ( ). La mayoría de las personas de este grupo son mujeres, todos


0.78125

corresponden a pasajeros de primera clase y el valor promedio que pagaron por su tickete
es el más elevado ( ) de todos los grupos.
222.03463

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 72/73


11/5/23, 15:10 M2U4_Aprendizaje_no_supervisado__agrupamiento

Recursos adicionales
Los siguientes enlaces corresponden a sitios en donde encontrará información muy útil para
profundizar en el conocimiento de las funcionalidades de la librería Scikit-learn en el
desarrollo y evaluación de modelos de aprendizaje no supervisado como el agrupamiento,
además de material de apoyo teórico para reforzar estos conceptos:
Clustering - Scikit-learn
Hierarchial Clustering - Scikit-learn
Column Transformer for heterogeneous data - Scikit-learn
Data Mining Cluster Analysis: Basic Concepts and Algorithms - Introduction to Data
Mining by Tan, Steinbach, Karpatne, Kumar
Cluster Analysis in Data Mining

Referencias
Manning, C., Raghavan, D. & Schütze, H. (2008). Introduction to Information Retrieval
[Introducción a la recuperación de información].

Créditos
Profesor: Fabio Augusto Gonzalez
Asistentes docentes:
Miguel Angel Ortiz Marín
Alberto Nicolai Romero Martínez
Universidad Nacional de Colombia - Facultad de Ingeniería
In [ ]:

file:///Users/mac/Downloads/M2U4_Aprendizaje_no_supervisado__agrupamiento (1).html 73/73


7/5/23, 16:00 grupo4

In [ ]: import numpy as np # algebra lineal


import matplotlib.pyplot as plt # visualizacion
from sklearn.svm import SVR # modelamiento
from sklearn.preprocessing import MinMaxScaler # transformar datos
from sklearn.model_selection import train_test_split # particionar los datos (v
from sklearn.metrics import mean_squared_error

plt.style.use("ggplot")

Carga de Datos
In [ ]: time = np.random.uniform(0, 10, size=(1000, 1))
position = np.exp(-0.2 * time) * np.cos(2 * time) + np.random.normal(0, 0.1, si

In [ ]: fig, ax = plt.subplots()
ax.scatter(time, position, alpha=0.2)
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

Preprocesamiento
In [ ]: np.exp(1000)

<ipython-input-11-47a6eab891c2>:1: RuntimeWarning: overflow encountered in exp


np.exp(1000)

file:///Users/mac/Downloads/grupo4.html 1/4
7/5/23, 16:00 grupo4
inf
Out[ ]:

In [ ]: np.exp(-0.1)

0.9048374180359595
Out[ ]:

x − min (x)

x =
max (x) − min (x)

In [ ]: scaler = MinMaxScaler().fit(time)
time_t = scaler.transform(time)

In [ ]: fig, ax = plt.subplots()
ax.scatter(time_t, position, alpha=0.2)
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

In [ ]: time_n = scaler.inverse_transform(time_t)
time_n.min(), time_n.max()

(0.003010875474882546, 9.98506355700189)
Out[ ]:

Validacion Cruzada
In [ ]: time_train, time_test, position_train, position_test = train_test_split(
time_t, position, test_size=0.3, random_state=0
file:///Users/mac/Downloads/grupo4.html 2/4
7/5/23, 16:00 grupo4
)

Modelamiento
In [ ]: model = SVR(gamma=100).fit(time_train, position_train)

/usr/local/lib/python3.9/dist-packages/sklearn/utils/validation.py:1143: DataC
onversionWarning: A column-vector y was passed when a 1d array was expected. P
lease change the shape of y to (n_samples, ), for example using ravel().
y = column_or_1d(y, warn=True)

Evaluacion
In [ ]: model.score(time_train, position_train)

0.9324830029391247
Out[ ]:

In [ ]: model.score(time_test, position_test)

0.9302732695254807
Out[ ]:

In [ ]: x_range = np.linspace(0, 1, 1000).reshape(-1, 1)


y_pred = model.predict(x_range)

In [ ]: fig, ax = plt.subplots()
ax.scatter(time_t, position, alpha=0.2)
ax.plot(x_range, y_pred, color="k")
ax.set(xlabel="Tiempo [s]", ylabel="Posicion [cm]")

[Text(0.5, 0, 'Tiempo [s]'), Text(0, 0.5, 'Posicion [cm]')]


Out[ ]:

file:///Users/mac/Downloads/grupo4.html 3/4
7/5/23, 16:00 grupo4

In [ ]: position_train.mean()

0.008677297702295469
Out[ ]:

In [ ]: y_pred = model.predict(time_test)
np.sqrt(mean_squared_error(position_test, y_pred))

0.09755202307869525
Out[ ]:

In [ ]: !jupyter nbconvert --to html /content

file:///Users/mac/Downloads/grupo4.html 4/4

También podría gustarte