Laboratorio 2 - Parte 1. KNN para un problema de clasificación#
!wget -nc --no-cache -O init.py -q https://raw.githubusercontent.com/jdariasl/Intro_ML_2025/master/init.py
import init; init.init(force_download=False); init.get_weblink()
from local.lib.rlxmoocapi import submit, session
import inspect
session.LoginSequence(endpoint=init.endpoint, course_id=init.course_id, lab_id="L02.01", varname="student");
#configuración del laboratorio
# Ejecuta esta celda!
from Labs.commons.utils.lab2 import *
_, x, y = part_1()
y = y.reshape(np.size(y), 1)
Tarea 1: Exploración de datos#
Usaremos el dataset iris para el problema de clasificación. En el UCI Machine Learning Repository se encuentra más información en el siguiente link .
print("muestra de los 5 primeros renglones de x:\n", x[0:5, :])
print("muestra de los 5 primeros renglones de y:\n", y[0:5])
print ("¿el resultado de esta instrucción que información nos brinda?", x.shape[0])
print ("¿el resultado de esta instrucción que información nos brinda?", x.shape[1])
print ("¿el resultado de esta instrucción que información nos brinda?", len(np.unique(y)))
En un problema de clasificación, tener un desbalance de muestras puede ser perjudicial para el proceso de entrenamiento. Vamos a crear una función para verificar el número de muestras por clases.
#Ejercicio de código
def muestras_por_clases (Y):
"""Función que calcula el número de muestras por cada clase
Y: vector de numpy con las etiquetas de las muestras del conjunto X
retorna: diccionario [int/float:int/float]
con la estructura:{etiquetaclase1: número de muestras clase1, etiquetaclase2: número de muestras clase2}
"""
dicto = {}
## Pista se puede asginar keys a diccionario: dict[etiqueta] = valor
for
return (dicto)
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T1');
# con esta linea de código puedes ver la dsitribución de forma gráfica
fig, ax = plt.subplots()
ax.bar(muestras_por_clases(y).keys(), muestras_por_clases(y).values())
ax.set_title("número de muestras por clase")
ax.set_xlabel("etiqueta de clase")
ax.set_ylabel("# muestras por clase")
ax.set_xticks(list(muestras_por_clases(y).keys()))
plt.show()
#Pregunta Abierta
#@markdown ¿Cómo calificaria la distribución de clases desde el punto de vista de un problema de clasificación?
respuesta = "" #@param {type:"string"}
Tarea 2: Clasificación con K-vecinos#
Recordemos los conceptos vistos en la teoría sobre los modelos basados en los K-vecinos más cercanos. En este ejercicio vamos a escribir la función que implementa este modelo. Pero primero vamos a definir la función que nos ayudará a calcular el error de clasificación.
def ErrorClas(Y_lest, Y):
"""función que calcula el error de clasificación
Y_lest: numpy array con las predicciones de etiqueta
Y: etiquetas reales
retorna: error de clasificación (int)
"""
error = 1 - np.sum(Y_lest == Y)/len(Y)
return error
Ahora si es hora del ejercicio. Ten en cuenta lo siguiente:
Pistas
Para el cáculo de la distancia entre vectores existen varias opciones:
Usar la función la distancia entre matrices
scipy.spatial.distance.cdist
(Ejemplo)–esta puede ser usada directamente comocdist(...)
. Entiende la salida de esta función. Al usarla, se logra un rendimiento superior.Usar la función de distancia euclidiana
scipy.spatial.distance.euclidean
(Ejemplo)–puede acceder a ella directamente comoeuclidean
. Aca debe pensar en un algoritmo elemento a elemento, por lo tanto menos eficiente.
También serán de utilidad las funciones
np.sort
ynp.argsort
.Ten presente que la moda es una operación que calcula el valor más común. En el notebook ya se encuentra cargada esta operacion, es posible usarla de esta manera :
mode(y)
# Ejercicio de codigo
def knn_clasificacion (k, X_train, Y_train, X_test):
""" Función que implementa el modelo de K-Vecino mas cercanos
para clasificación
k (int): valor de vecinos a usar
X_train: es la matriz con las muestras de entrenamiento
Y_train: es un vector con los valores de salida para cada una de las muestras de entrenamiento
X_test: es la matriz con las muestras de validación
retorna: las estimaciones del modelo KNN para el conjunto X_test
esta matriz debe tener un shape de [row/muestras de X_test]
y las distancias de X_test respecto a X_train, esta matrix
debe tener un shape de [rows de X_test, rows X_train]
lo que es lo mismo [muestras de X_test, muestras de X_train]
"""
if k > X_train.shape[0]:
print("k no puede ser menor que las muestras de entrenamiento")
return(None)
distancias =
Yest = np.zeros(X_test.shape[0])
for
return (Yest, distancias)
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T2');
Tarea 3: Evaluar el modelo de K-vecinos#
Ahora vamos a probar nuestro algoritmo. Pero antes de esto, debemos dividir nuestro conjunto de datos, para lo cual usaremos una función llamada train_test_split de la libreria sklearn. Aca puedes ver la ayuda. Entiende su funcionamiento. Vamos a usarla para crear una función con una propoción fija de 80%-20% entre nuestro conjunto de entrenamiento y de pruebas.
#ejercicio de codigo
def train_test_split_fix(X, y, test_size=..., random_state=...):
"""función que divide el conjunto de datos en
entrenamiento y pruebas usando una proporción
fija de 20 % (test_size) para el conjunto de pruebas.
Fije la semilla en cero para que los resultados sean
reproducibles.
X: matriz de numpy con las muestras y características
Y: matriz de numpy con las etiquetas reales
retorna:
Xtrain: conjunto de datos para entrenamiento
Xtest: conjunto de datos para pruebas
Ytrain: conjunto de etiquetas para entrenamiento
Ytest: conjunto de etiquetas para prueba
"""
X_train, X_test, y_train, y_test = ( ...)
return (X_train, X_test, y_train, y_test)
Vamos a proceder a experimentar. Para ello vamos a crear una función que realiza los experimentos usando las funciones previamente construidas. En el código se hace uso de la función StandardScaler, para normalizar los datos.
#Ejercicio de código
def experimentar (ks, X, Y):
"""Función que realiza los experimentos con knn usando
una estrategia de validacion entrenamiento y pruebas
ks: List[int/float] lista con los valores de k-vecinos a usar
X: matriz de numpy conjunto con muestras y caracteristicas
Y: vector de numpy con los valores de las etiquetas
retorna: dataframe con los resultados
"""
# dividimos usando la función
Xtrain, Xtest, Ytrain, Ytest = train_test_split_fix(X,Y,test_size=0.2, random_state=0)
# se llama el objeto
scaler = StandardScaler()
# Se calculan los parametros
scaler.fit(Xtrain)
# se usa el objeto con los parametros calculados
# realizar la normalización
Xtrain= scaler.transform(Xtrain)
Xtest = scaler.transform(Xtest)
resultados = pd.DataFrame()
idx = 0
for k in ks:
# iteramos sobre la lista de k's
resultados.loc[idx,'k-vecinos'] = k
Yest, dist = ...
resultados.loc[idx,'error de prueba'] = ...
idx+=1
return (resultados)
#@title Pregunta Abierta
#@markdown ¿Qué tipo de normalización ejecuta la función `StandardScaler`?
respuesta = "" #@param {type:"string"}
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T3');
Ahora ejecuta los experimentos con k = 2,3,4,5,6,7,10
resultados = experimentar ([2,3,4,5,6,7,10], x, y)
resultados
Tarea 4: Ventana de Parzen#
Ahora vamos a utilizar el metodo de ventana de parzen. Debemos recordar de las clases teóricas, que para aplicar este método, debemos usar una función kernel. En la siguiente celda se proponen dos funciones para:
Cálculo de un kernel gausiano
Cálculo de la ventana de parzen, es decir del término: \( \sum_{i=1}^{N} K(u_i)\), siendo \(\;\; u_i = \frac{d({\bf{x}}^*,{\bf{x}}_i)}{h}\) y la función \(K\) el kernel gausiano
def kernel_gaussiano(x):
"""Calcula el kernel gaussiano de x
x: matriz/vector de numpy
retorna: el valor de de kernel gaussiano
"""
return np.exp((-0.5)*x**2)
def ParzenWindow(x,Data,h):
""""ventana de parzen
x: vector con representando una sola muestra
Data: vector de muestras de entrenamiento
h: ancho de la ventana de kernel
retorna: el valor de ventana de parzen para una muestra
"""
h = h
Ns = Data.shape[0]
suma = 0
for k in range(Ns):
u = euclidean(x,Data[k,:])
suma += kernel_gaussiano(u/h)
return suma
#@title Pregunta Abierta
#@markdown ¿Qué objetivo tiene la función kernel? Contestar en el contexto del método de la ventana de parzen
respuesta = "" #@param {type:"string"}
Una vez entendidos los anteriores metodos, los vamos a usar para resolver el ejercicio de código.
#ejercicio de código
def parzenClass(h, Xtrain, Ytrain, Xtest):
""" Función que implementa el método de ventana de parzen
para clasificación
h (float): ancho de la ventana
Xtrain: matriz con las muestras de entrenamiento
Ytrain: vector con los valores de salida para cada una de las muestras de entrenamiento
retorna: - las estimaciones del modelo parzen para el conjunto Xtest
esta matriz debe tener un shape de [row/muestras de Xtest]
- las probabilidades obtenidas con el método de vetana de Parzen para cada clase [row/muestras de Xtest, número de clases]
"""
## pista: recuerde el termino que acompaña al sumatoria (N)
for
#Debe retornar un vector que contenga las predicciones para cada una de las muestras en Xtest, en el mismo orden.
return Yest, fds_matrix
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T4');
Tarea 5 - Validar modelo ventana con Parzen#
Ahora vamos a realizar los experimentos, recordar usar la misma metodologia de validación, usando la función previamente creada.
#ejercicio de código
def experimentarParzen (hs, X, Y):
"""Función que realiza los experimentos con el modelo de Venatan de Parzen usando
una estrategia de validación con entrenamiento y pruebas
hs: List[int/float] lista con los valores de h a usar
X: matriz con el conjunto de muestras de la base de datos
Y: vector de numpy con los valores de las etiquetas de clase de cada muestra
retorna: dataframe con los resultados, debe contener las siguientes columnas:
- el ancho de la ventana y el error medio de prueba
"""
resultados = pd.DataFrame()
idx = 0
Xtrain, Xtest, Ytrain, Ytest = ...(X,Y)
scaler = StandardScaler()
#normalizamos los datos
scaler.fit(Xtrain)
Xtrain = scaler.transform(Xtrain)
Xtest = scaler.transform(Xtest)
# iteramos sobre los valores de hs
for h in hs:
Yest, probabilidades = ...
resultados.loc[idx,'ancho de ventana'] = h
resultados.loc[idx,'error de prueba'] = ...
idx+=1
return (resultados)
Registra tu solución en línea
student.submit_task(namespace=globals(), task_id='T5');
hs = [0.05, 0.1, 0.5, 1, 2, 5, 10]
experimentos_parzen = experimentarParzen(hs,x,y)
experimentos_parzen
#@title Pregunta Abierta
#@markdown ¿En el método de ventana de parzen, porqué no hay necesidad de definir el número de vecinos cercanos?
respuesta = "" #@param {type:"string"}
#@title Pregunta Abierta
#@markdown ¿Por qué el KNN y la ventana de parzen son modelos no parámetricos?
respuesta = "" #@param {type:"string"}