Construir modelos de aprendizaje supervisados para resolver tareas de clasificación mediante el algoritmo de vecinos más cercanos con distintos conjuntos de datos.
Inicialmente se cagan las librerías necesarias para ejecutar funciones relacionadas con el algoritmo supervisado de vecinos mas cercanos.
Se cargan los datos necesarios, se simula con datos de características de Frutas, Proteínas y Vegetales.
Se visualizan los puntos de cada ingrediente.
Se construye el modelo a partir de todos los datos
Se analiza y se interpreta el modelo.
Se realizan clasificaciones con datos de validación o nuevos datos.
Se evalúa la precisión del modelo con la matriz de confusión.
Los algoritmos de clasificación utilizan las variables independientes y sus valores o características y la relación que tienen con una variable dependiente que se define con una etiqueta.
Estos algoritmos supervisados utilizan los datos de entrenamiento para crear un modelo que se aplica para clasificar conforme a la etiqueta establecida (variable dependiente) un conjunto de datos de validación o nuevos valores.
En las tareas de clasificación, se aprende de la estructura de un conjunto de datos. Aprender las diferentes categorías se consigue utilizando un modelo. Este modelo ayuda a aproximar los identificadores de un grupo (Jones 2019).
Los clasificadores de vecinos más cercanos (KNN) se definen por su característica (variables independientes) de clasificar ejemplos o nuevos valores sin etiquetar asignándoles la clase de los ejemplos etiquetados más similares (Lantz 2013).
Para encontrar la cercanía entre dos puntos se utiliza el concepto de la distancia Euclidiana, que es un número positivo que indica la separación que tienen dos puntos en un espacio donde se cumplen los axiomas y teoremas de la geometría de Euclides (Pérez, n.d.).
Figura 1. Distancia Euclidiana [@pérez].
\[ dis.euc(p, q) = \sqrt{(p_1-q_1)^2 + (p_2-q_2)^2} \]
Siendo \(p\) y \(q\) registros, ejemplos, instancias u observaciones a ser comparadas.
El término característica de un conjunto de datos se refiere a variables independientes y el término variable resultado o etiqueta es la variable dependiente.
El término de \(p_1\) se refiere al valor de la primera característica del registro \(p\) mientras que el término \(q_1\) se refiere al valor de la primera característica del registro \(q\).
El término de \(p_2\) se refiere al valor de la segunda característica del registro \(p\) y el término \(q_2\) se refiere al valor de la segunda característica del registro \(q\).
La fórmula para \(n\) dimensiones involucra más de dos características.
\[ dis.euc(p, q) = \sqrt{(p_1-q_1)^2 + (p_2-q_2)^2 + ... + (p_n-q_n)^2} \]
Siendo \(n\) el número de características o variable independientes.
library(class) # Funciones para clasificación
library(caret) # Funciones para Clasificación y Regresión
library(mlbench) # Funciones para Machine Learning
library(e1071) # Estadísticas
library(ggplot2) # Gráficas
library(dplyr) # Operaciones con datos
library(knitr) # Para tablas amigables
En este ejercicio se tiene por cada ingrediente variables que tienen en una escala de 0-10 un valor de tipo de dulzura y consistencia crujiente. El algoritmo determina o predice que tipo de comida si es fruta, que se distingue su contenido en proteína y vegetal [Fruta, Proteína, Vegetal].
ingrediente <- c('Manzana', 'Tocino', 'Plátano', 'Zanahoria', 'Apio', 'Queso', 'Nuez', 'Camarón', 'Pescado', 'Naranja', 'Pepino', 'Pera', 'Uva', 'Lechuga', 'Frijol', 'Maiz')
crujiente <- c(8.5, 2.5, 6, 9.5, 5.4, 3, 5, 4, 3, 6, 6.6, 9.1, 2, 6, 3.4, 3.3)
dulce <- c(9, 1, 6, 4, 2, 1, 1.2, 1.2, 2, 9, 5, 9.4, 10, 5, 1.2, 1.3)
tipo.comida <- c('Fruta', 'Proteína', 'Fruta', 'Vegetal', 'Vegetal', 'Proteína', 'Proteína', 'Proteína', 'Proteína', 'Fruta', 'Vegetal', 'Fruta', 'Fruta', 'Vegetal', 'Proteína', 'Proteína')
datos <- data.frame(ingrediente, crujiente, dulce, tipo.comida)
kable(datos, caption = "Datos")
ingrediente | crujiente | dulce | tipo.comida |
---|---|---|---|
Manzana | 8.5 | 9.0 | Fruta |
Tocino | 2.5 | 1.0 | Proteína |
Plátano | 6.0 | 6.0 | Fruta |
Zanahoria | 9.5 | 4.0 | Vegetal |
Apio | 5.4 | 2.0 | Vegetal |
Queso | 3.0 | 1.0 | Proteína |
Nuez | 5.0 | 1.2 | Proteína |
Camarón | 4.0 | 1.2 | Proteína |
Pescado | 3.0 | 2.0 | Proteína |
Naranja | 6.0 | 9.0 | Fruta |
Pepino | 6.6 | 5.0 | Vegetal |
Pera | 9.1 | 9.4 | Fruta |
Uva | 2.0 | 10.0 | Fruta |
Lechuga | 6.0 | 5.0 | Vegetal |
Frijol | 3.4 | 1.2 | Proteína |
Maiz | 3.3 | 1.3 | Proteína |
ggplot(data = datos, mapping = aes(x = dulce, y = crujiente)) +
geom_point(aes(colour = factor(tipo.comida), shape = factor(tipo.comida)), size=4)
¿Que sucede si llega un nuevo registro ?, por ejemplo un tomate con valor de \(dulce = 6\) y \(crujiente = 3\), ¿a que grupo o tipo de comida pertenece: Fruta, Proteína o Vegetal?
Cálculo por medio de distancia euclidiana comparando el nuevo valor con todos los anteriores.
Se utiliza un valor de \(k\) vecinos que significa cuales son los más cercanos; si \(k=1\) significa que sólo se toma al registro más cercano y con eso se toma la clasificación.
Si \(k=3\) se toman los tres ejemplos más cercanos y se vota y se toma la decisión de cual es mayoría.
Si \(k=5\) de la misma manera se define cuáles y cuántos son los más cercanos y se toma la mayoría para clasificar el nuevo ejemplo.
ingrediente <- 'Tomate'
crujiente <- 3
dulce <- 6
tipo.comida = '????????'
nuevos.datos <- data.frame (ingrediente, crujiente, dulce, tipo.comida)
kable(nuevos.datos, caption = "Nuevos datos o ejemplo 'Tomate'")
ingrediente | crujiente | dulce | tipo.comida |
---|---|---|---|
Tomate | 3 | 6 | ???????? |
Obviamente el cerebro humado debe identificar que el tomate es un vegetal sin necesidad de ningún algoritmo, pero se va a simular un aprendizaje supervisado para resolver tarea de clasificación.
Con la siguiente gráfica se identifica el nuevo dato con un punto en color negro, se puede observar que este punto se encuentra tan cerca de los vegetales como de las frutas.
¿A cuál grupo pertenece o se clasifica?
ggplot(data = datos, mapping = aes(x = dulce, y = crujiente)) +
geom_point(aes(colour = factor(tipo.comida), shape = factor(tipo.comida)), size=4) +
geom_point(aes(x = nuevos.datos$dulce, y = nuevos.datos$crujiente, size = 4))
¿Cuál será la distancia euclidiana entre las características del tomate y las demás características de los otros ingredientes?
Hacer el valor de \(k=1\) es decir, se elegirá el ejemplo más cercano y con ello se toma la decisión y se clasifica al nuevo registro.
formula = paste("sqrt((",nuevos.datos$crujiente, "-",datos$crujiente,")^2","+","(",nuevos.datos$dulce,"-",datos$dulce,")^2)")
dist.euc = sqrt((nuevos.datos$crujiente-datos$crujiente)^2+(nuevos.datos$dulce-datos$dulce)^2)
datos.dist <- cbind(datos, formula, dist.euc)
kable(datos.dist, caption = "Distancia Euclidina 'Tomate' y resto ingredientes'")
ingrediente | crujiente | dulce | tipo.comida | formula | dist.euc |
---|---|---|---|---|---|
Manzana | 8.5 | 9.0 | Fruta | sqrt(( 3 - 8.5 )^2 + ( 6 - 9 )^2) | 6.264982 |
Tocino | 2.5 | 1.0 | Proteína | sqrt(( 3 - 2.5 )^2 + ( 6 - 1 )^2) | 5.024938 |
Plátano | 6.0 | 6.0 | Fruta | sqrt(( 3 - 6 )^2 + ( 6 - 6 )^2) | 3.000000 |
Zanahoria | 9.5 | 4.0 | Vegetal | sqrt(( 3 - 9.5 )^2 + ( 6 - 4 )^2) | 6.800735 |
Apio | 5.4 | 2.0 | Vegetal | sqrt(( 3 - 5.4 )^2 + ( 6 - 2 )^2) | 4.664762 |
Queso | 3.0 | 1.0 | Proteína | sqrt(( 3 - 3 )^2 + ( 6 - 1 )^2) | 5.000000 |
Nuez | 5.0 | 1.2 | Proteína | sqrt(( 3 - 5 )^2 + ( 6 - 1.2 )^2) | 5.200000 |
Camarón | 4.0 | 1.2 | Proteína | sqrt(( 3 - 4 )^2 + ( 6 - 1.2 )^2) | 4.903060 |
Pescado | 3.0 | 2.0 | Proteína | sqrt(( 3 - 3 )^2 + ( 6 - 2 )^2) | 4.000000 |
Naranja | 6.0 | 9.0 | Fruta | sqrt(( 3 - 6 )^2 + ( 6 - 9 )^2) | 4.242641 |
Pepino | 6.6 | 5.0 | Vegetal | sqrt(( 3 - 6.6 )^2 + ( 6 - 5 )^2) | 3.736308 |
Pera | 9.1 | 9.4 | Fruta | sqrt(( 3 - 9.1 )^2 + ( 6 - 9.4 )^2) | 6.983552 |
Uva | 2.0 | 10.0 | Fruta | sqrt(( 3 - 2 )^2 + ( 6 - 10 )^2) | 4.123106 |
Lechuga | 6.0 | 5.0 | Vegetal | sqrt(( 3 - 6 )^2 + ( 6 - 5 )^2) | 3.162278 |
Frijol | 3.4 | 1.2 | Proteína | sqrt(( 3 - 3.4 )^2 + ( 6 - 1.2 )^2) | 4.816638 |
Maiz | 3.3 | 1.3 | Proteína | sqrt(( 3 - 3.3 )^2 + ( 6 - 1.3 )^2) | 4.709565 |
Ordenando los datos con las distancias de manera ascendente por el valor de la distancia euclidiana.
datos.dist <- arrange(datos.dist, dist.euc)
kable(datos.dist, caption = "Distancia Euclidina 'Tomate' y resto ingredientes', ordenado ascendentemente")
ingrediente | crujiente | dulce | tipo.comida | formula | dist.euc |
---|---|---|---|---|---|
Plátano | 6.0 | 6.0 | Fruta | sqrt(( 3 - 6 )^2 + ( 6 - 6 )^2) | 3.000000 |
Lechuga | 6.0 | 5.0 | Vegetal | sqrt(( 3 - 6 )^2 + ( 6 - 5 )^2) | 3.162278 |
Pepino | 6.6 | 5.0 | Vegetal | sqrt(( 3 - 6.6 )^2 + ( 6 - 5 )^2) | 3.736308 |
Pescado | 3.0 | 2.0 | Proteína | sqrt(( 3 - 3 )^2 + ( 6 - 2 )^2) | 4.000000 |
Uva | 2.0 | 10.0 | Fruta | sqrt(( 3 - 2 )^2 + ( 6 - 10 )^2) | 4.123106 |
Naranja | 6.0 | 9.0 | Fruta | sqrt(( 3 - 6 )^2 + ( 6 - 9 )^2) | 4.242641 |
Apio | 5.4 | 2.0 | Vegetal | sqrt(( 3 - 5.4 )^2 + ( 6 - 2 )^2) | 4.664762 |
Maiz | 3.3 | 1.3 | Proteína | sqrt(( 3 - 3.3 )^2 + ( 6 - 1.3 )^2) | 4.709565 |
Frijol | 3.4 | 1.2 | Proteína | sqrt(( 3 - 3.4 )^2 + ( 6 - 1.2 )^2) | 4.816638 |
Camarón | 4.0 | 1.2 | Proteína | sqrt(( 3 - 4 )^2 + ( 6 - 1.2 )^2) | 4.903060 |
Queso | 3.0 | 1.0 | Proteína | sqrt(( 3 - 3 )^2 + ( 6 - 1 )^2) | 5.000000 |
Tocino | 2.5 | 1.0 | Proteína | sqrt(( 3 - 2.5 )^2 + ( 6 - 1 )^2) | 5.024938 |
Nuez | 5.0 | 1.2 | Proteína | sqrt(( 3 - 5 )^2 + ( 6 - 1.2 )^2) | 5.200000 |
Manzana | 8.5 | 9.0 | Fruta | sqrt(( 3 - 8.5 )^2 + ( 6 - 9 )^2) | 6.264982 |
Zanahoria | 9.5 | 4.0 | Vegetal | sqrt(( 3 - 9.5 )^2 + ( 6 - 4 )^2) | 6.800735 |
Pera | 9.1 | 9.4 | Fruta | sqrt(( 3 - 9.1 )^2 + ( 6 - 9.4 )^2) | 6.983552 |
Para \(k=1\) el más cercano es una fruta pero para \(k=3\) la mayoría de los cercanos son vegetales; para \(k=5\) hay empate entre frutas y vegetales; para \(K=7\) hay empate en frutas y vegetales.
La pregunta es: ¿cuál debe ser el valor de \(k\)?.
Si se pone \(k=N\) igual al total de registros, el modelo falla porque entones si hay mayoría (overfitting) de un tipo entonces ese siempre gana.
Si se establece \(K = 1\) entonces el modelo puede generar ruido y/o outliers que sean influyentes en el nuevo registro.
La respuesta está dada de acuerdo a la cantidad de datos. Se recomienda la raíz cuadrada de la cantidad de datos; es decir para este caso si hay 16 registros la raíz es \(4\).
Y la otra pregunta es ¿si hay un empate por cuál se decide el algoritmo, tal vez por los primeros.
Se aplica la función knn() de la librería o paquete class en donde se la pasan parámetros tales como los datos por lo cual se entrena el modelo y los datos de validación que en este caso es el nuevo dato o ejemplo del tomate, también se necesita el valor de \(k\) y finalmente se le indica la variable con la etiqueta o la variable dependiente por la cual se clasifica.
Los valores datos[,c(2,3) indican las características de crujiente y dulce.
Los valores nuevos.datos[,c(2,3)] indican las características de crujiente y dulce del nuevo registro o del ejemplo nuevo.
modelo <- knn(train = datos[,c(2,3)], test = nuevos.datos[,c(2,3)], k = 4, cl = datos[,4])
modelo
## [1] Vegetal
## Levels: Fruta Proteína Vegetal
summary(modelo)
## Fruta Proteína Vegetal
## 0 0 1
¿Cuántos datos se pueden clasificar ?, probar con mas de un dato.
ingrediente <- c('Granada', 'Almendra')
crujiente <- c(5.5, 4.8)
dulce <- c(7.4, 1.8)
tipo.comida = c('???', '???')
nuevos.datos <- data.frame (ingrediente, crujiente, dulce, tipo.comida)
kable(nuevos.datos, caption = "Nuevos datos o ejemplo 'Tomate'")
ingrediente | crujiente | dulce | tipo.comida |
---|---|---|---|
Granada | 5.5 | 7.4 | ??? |
Almendra | 4.8 | 1.8 | ??? |
modelo <- knn(train = datos[,c(2,3)], test = nuevos.datos[,c(2,3)], k = 4, cl = datos[,4])
modelo
## [1] Vegetal Proteína
## Levels: Fruta Proteína Vegetal
summary(modelo)
## Fruta Proteína Vegetal
## 0 1 1
paste("La ", nuevos.datos[1,1], " se clasifica como: ", modelo[1])
## [1] "La Granada se clasifica como: Vegetal"
paste("La ", nuevos.datos[2,1], " se clasifica como: ", modelo[2])
## [1] "La Almendra se clasifica como: Proteína"
Para un nuevo registro con valores de crujiente = 3 y dulce = 6, haciendo las operaciones de la distancia euclidiana y por medio de la función knn() la clasificación de ese nuevo ingrediente es que debe ser un vegetal de entre las etiquetas frutas, vegetal o proteína.
El ejercicio demuestra que mediante la fórmula de distancia euclidiana y por medio de la función knn() se clasifica con el mismo resultado.
Al haber introducido más de un registro la función knn responde y genera de igual forma resultados determinando la etiqueta a la que le pertenecen los nuevos datos utilizando la historia de los datos, siendo éste un algoritmo supervisado que resuelve tareas de de clasificación.
¿Cuál debe ser el mejor \(k\)?. Otra manera de ver cual es el mejor valor de \(k\) es mediante datos de entrenamiento y validación a partir de los datos origen y mediante matrices de confusión evaluar la exactitud (accuracy) del modelo, es decir el porcentaje de aciertos con los datos de validación y elegir al mejor número de vecinos \(k\) de entre varios de ellos.
¿Qué sucede si hay más características?, se utiliza la fórmula de distancia euclidiana para \(n\) o múltiples características.
¿Qué sucede si hay muchos registros?, puede usar este algoritmo o utilizar otros algoritmos como árboles de clasificación o regresión logística o random forest, entre otros..