Laboratorio 2 - Parte 1. KNN para un problema de clasificación
Contents
Recuerda que una vez abierto, Da clic en “Copiar en Drive”, de lo contrario no podras alamancenar tu progreso
Nota: no olvide ir ejecutando las celdas de código de arriba hacia abajo para que no tenga errores de importación de librerías o por falta de definición de variables.
#configuración del laboratorio
# Ejecuta esta celda!
%load_ext autoreload
%autoreload 2
# for local
#import sys ; sys.path.append('../commons/utils/')
!wget https://raw.githubusercontent.com/jdariasl/ML_2020/master/Labs/commons/utils/general.py -O general.py
from general import configure_lab2
configure_lab2()
from lab2 import *
GRADER, x, y = part_1()
Laboratorio 2 - Parte 1. KNN para un problema de clasificación¶
Ejercicio 1: Contextualización del problema¶
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 de más de una clase, 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):
"""Funcion 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)
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio1", muestras_por_clases)
# con esta linea de codigo puedes ver la dsitribucion de forma grafica
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()
#@title Pregunta Abierta
#@markdown ¿dependiendo de los resultados de la informacion anterior, como calificaria la distribución de clases desde el punto de vista de la factibilidad de usarlo este dataset para el problema planteado?
respuesta_1 = "" #@param {type:"string"}
Ejercicio 2: Completar código KNN¶
Recordemos los conceptos vistos en teoria para los modelos basados en los K-vecimos 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 ayudara calcular el error de clasificación.
def ErrorClas(Y_lest, Y):
"""funcion que calcula el error de clasificación
Y_lest: numpy array con la estimaciones 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 la distancia euclidiana
scipy.spatial.distance.euclidean
(Ejemplo)–pueder 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):
""" Funcion 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 pra 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, estan 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)
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio2", KNN_Clasificacion)
Ejercicio 3: Experimentos de KNN¶
Ahora vamos a probar nuestro algoritmo. Pero antes de esto vamos a tener que dividir nuestro conjunto de datos, vamos a usar 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.
NOTA
cuando se este usando SKlearn, se quiere incentivar la lectura de la documentación, por lo tanto el calificador va buscar siempre que se llamen de manera explicita los parametros que se estan usando. Ejemplo si se va usar un parametro llamado shuffle
se debe llamar como funcion_a_usar(shuffle = True)
.
#ejercicio de codigo
def train_test_split_fix(X,Y):
"""funcion que divide el conjunto de datos en
entrenamiento y pruebas
usando un proporcion fija de 20 %
para el conjunto de pruebas.
X: matriz de numpy con las muestras y caractersiticas
Y: matriz de numpy con las 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
"""
Xtrain, Xtest, Ytrain, Ytest = ( ...)
return (Xtrain, Xtest, Ytrain, Ytest)
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio3", train_test_split_fix)
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)
# 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 =
errorTest =
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_2 = "" #@param {type:"string"}
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio4", experimentar)
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
Ejercicio 4: ventana de Parzen¶
Ahora vamos a utilizar el metodo de ventana de parzen. Recordar de las clases teoricas, que para aplicar este metodo, debemos usar una función kernel. En la siguiente celda se proponen dos funciones para:
calculo de un kernel gausiano
calculo de la ventana de parzen, es decir el termino: \( \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_3 = "" #@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, X_train, Y_train, X_test):
""" Funcion que implementa metodo de ventana de parzen para
para clasificación
h (float): ancho de h de la ventana
X_train: es la matriz con las muestras de entrenamiento
Y_train: es un vector con los valores de salida pra cada una de las muestras de entrenamiento
X_test: es la matriz con las muestras de validación
retorna: - las estimaciones del modelo parzen para el conjunto X_test
esta matriz debe tener un shape de [row/muestras de X_test]
- las probabilidades de la vetana [row/muestras de X_test, número de clases]
"""
Yest = np.zeros(X_test.shape[0])
clases = np.unique(Y_train)
fds_matrix = np.zeros((X_test.shape[0], len(clases)))
## pista: recuerde el termino que acompaña al sumatoria (N)
for n, sample in enumerate (X_test):
for label in clases:
#Debe retornar un vector que contenga las predicciones para cada una de las muestras en X_val, en el mismo orden.
return Yest, fds_matrix
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio5", parzenClass)
Ejercicio 5 - Experimentos con Parzen¶
Ahora vamos a realizar los experimentos, recordar usar la misma metodologia de validación, usando la función previamente creada.
#ejercicio de codigo
def experimentarParzen (hs, X, Y):
"""Función que realiza los experimentos con knn usando
una estrategia de validacion entrenamiento y pruebas
hs: List[int/float] lista con los valores de h 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, debe contener las siguientes columnas:
- el ancho de ventana, el error medio de prueba, la desviacion estandar del error
"""
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 =
errorTest =
resultados.loc[idx,'ancho de ventana'] = h
resultados.loc[idx,'error de prueba'] =
idx+=1
return (resultados)
## la funcion que prueba tu implementacion
GRADER.run_test("ejercicio6", experimentarParzen)
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_4 = "" #@param {type:"string"}
#@title Pregunta Abierta
#@markdown ¿por qué el KNN y la ventana de parzen son modelos no parámetricos?
respuesta_5 = "" #@param {type:"string"}
GRADER.check_tests()
#@title Integrantes
codigo_integrante_1 ='' #@param {type:"string"}
codigo_integrante_2 = '' #@param {type:"string"}
esta linea de codigo va fallar, es de uso exclusivo del los profesores
GRADER.grade()