10 K vecinos más próximos (KNN)
La regresión de K veinos más próximos (KNN por sus siglas en inglés) es un método no paramétrico que, de manera intuitiva, aproxima la asociación entre las variables independientes y el resultado continuo promediando las observaciones en la misma vecindad. El usuario debe establecer el número de vecinos o puede elegirlo mediante validación cruzada (lo veremos más adelante) para seleccionar el tamaño que minimiza el error cuadrático medio.
Si bien el método es bastante atractivo, rápidamente se vuelve poco práctico cuando aumenta la dimensión, es decir, cuando hay muchas variables independientes.
Los métodos del vecino más cercano proporcionan una forma bastante diferente de construir clasificadores y tienen fortalezas (supuestos mínimos, límites de decisión flexibles) y debilidades (carga computacional, falta de interpretabilidad) en comparación con los modelos de regresión logística.
En principio, la idea es sencilla. Recordemos que el conjunto de entrenamiento tendrá tanto predictores \(X\) como una variable resultado \(Y\) mientras que el conjunto de prueba sólo tendrá valores de \(X\) conocidos. Ambos conjunto de datos son necesarios.
Comenzamos eligiendo un número entero positivo \(k\) que especificará el número de vecinos que se utilizarán en la clasificación. Para clasificar un punto \(x\) en el conjunto test, se buscarán los \(k\) puntos más cercanos en el conjunto de entrenamiento, y se elige la clase que tenga la representación más alta entre los \(k\) puntos. De aquí que el algoritmo se llame KNN (“k vecinos más cercanos”).
Por ejemplo, supongamos que \(k=10\) y los 10 vecinos más cercanos a \(x\) tienen clases 1,1,3,2,3,3,3,2,3,2. Como hay cinco 3, tres 2 y dos 1, el punto \(x\) se asigna a la clase 3. Supongamos que para otro \(x\) los 10 vecinos más cercanos tienen clases 1,1,1,2,3,1,3,3,3,2. En este caso hay cuatro 1s y cuatro 3s, por lo que hay un empate en el liderato. El algoritmo vecino más cercano elegirá entre 1 y 3 al azar.
Aunque, en principio, KNN es sencillo, surgen algunos problemas. Primero, ¿cómo deberíamos elegir \(k\)? No hay una respuesta fácil, pero puede ayudar pensar en los valores extremos para \(k\). Podemos seleccionar \(k\) lo mas grande posible. Por ejemplo, supongamos que el conjunto de entrenamiento tiene 10 observaciones, con clases 1,1,1,2,2,2,3,3,3,3. Para cualquier punto del conjunto de prueba, \(k=10\) los vecinos más cercanos incluirán TODOS los puntos del conjunto de entrenamiento y, por lo tanto, cada punto del conjunto de prueba se clasificará en la clase 3. Este clasificador tiene una varianza baja (cero), pero probablemente un sesgo alto.
Si seleccionamos \(k\) lo mas pequeño posible, estaríamos en el caso \(k=1\). En este caso, cada punto del conjunto de prueba se coloca en la misma clase que su vecino más cercano en el conjunto de entrenamiento. Esto puede conducir a un clasificador de alta varianza y muy irregular, pero el sesgo tenderá a ser pequeño.
Un segundo tema que es relativamente fácil de abordar se refiere a las escalas en las que se miden los valores de \(x\). Si por ejemplo una variable \(x\) tienen un rango de 2 a 4, mientras que otra tiene un rango de 2000 a 4000, la distancia estará dominada por la segunda variable. La solución que se suele utilizar es normalizar todas las variables (cambiar su escala para que su media sea 0 y su desviación estándar sea 1).
Existen muchas liberías para llevar a cabo KNN. Nosotros usaremos la libería class
para ilustrar el ejemplo de cáncer de mama visto anteriormente y para el que ya hemos normalizado nuestros predictores.
library(class)
# para asegurarnos que usamos knn de class
<- class::knn(train=select(breast_train_prep, !diagnosis),
fit.knn test=select(breast_test_prep, !diagnosis),
cl=breast_train_prep$diagnosis,
k=10, prob=TRUE)
head(fit.knn)
[1] M M M M M M
Levels: B M
La probabilidad que cada individuo de la muestra test pertenezca al grupo asignado la podemos obtener con
<- attr(fit.knn, "prob")
prob head(prob)
[1] 1.0 1.0 1.0 0.6 0.7 1.0
A continuación podemos crear un gráfico. Esto es algo complejo, ya que queremos representar los datos de la muestra test coloreados por la clase a la que fueron asignados por el clasificador kNN (como fondo de la imagen) con los datos de entrenamiento (usando un símbolo diferente) y el límite de decisión. Este gráfico sólo lo podemos hacer para dos variables predictoras. Por ejemplo symmetry_worst
y texture_se
.
<- breast_test_prep %>%
plot.df select(symmetry_worst, texture_se ) %>%
mutate(predicted = as.factor(fit.knn))
# Usamos un 'convex hull' para determinar los puntos que determinan los límites
# de cada cluster
<- data.frame(x = plot.df$symmetry_worst,
plot.df1 y = plot.df$texture_se,
predicted = plot.df$predicted)
<- function(df) df[chull(df$x, df$y), ]
find_hull <- plyr::ddply(plot.df1, .variables = "predicted", .fun = find_hull)
boundary
ggplot(plot.df1, aes(x, y, color = predicted, fill = predicted)) +
geom_point(size = 3) +
geom_polygon(data = boundary, aes(x,y), alpha = 0.5) +
xlab("symmetry_worst") + ylab("texture_se")
Podemos ver la matriz de confusión
<- table(predict=fit.knn, Actual=breast_test_prep$diagnosis)
tt tt
Actual
predict B M
B 105 9
M 2 54
y la precisión es
sum(diag(tt)/sum(tt))
[1] 0.9352941
Podemos repetir los mismos cálculos para \(k=15\)
<- class::knn(train=select(breast_train_prep, !diagnosis),
fit.knn2 test=select(breast_test_prep, !diagnosis),
cl=breast_train_prep$diagnosis,
k=10)
fit.knn
[1] M M M M M M B B M M M B B M B B M B B M B M M B M B B B B B B M B B M M B B B B B B B B M M B B M
[50] B B M B B M B M M B B M M B M B B M B B M B M M M M B B B M B B B M B B B B B B B B B B M M B M M
[99] M B B B B M B B B B B B B M B B B B B B B B B B M B B B B M B M M B B B M B M B B B B B B B M M B
[148] B B M B B B B B M B B B B B B B B B B M M M B
attr(,"prob")
[1] 1.0 1.0 1.0 0.6 0.7 1.0 0.5 1.0 1.0 0.9 0.6 1.0 1.0 1.0 1.0 1.0 0.9 1.0 0.6 1.0 1.0 1.0 0.8 0.8
[25] 0.5 1.0 1.0 0.6 1.0 1.0 0.9 0.9 0.6 1.0 0.8 0.8 0.8 1.0 1.0 1.0 1.0 1.0 0.6 1.0 1.0 1.0 0.9 1.0
[49] 1.0 0.7 1.0 1.0 1.0 1.0 0.8 1.0 0.9 0.9 1.0 0.6 0.7 1.0 0.7 1.0 0.5 1.0 1.0 1.0 0.8 1.0 0.7 1.0
[73] 1.0 1.0 1.0 1.0 1.0 0.9 0.5 1.0 1.0 0.9 0.5 1.0 0.6 1.0 1.0 1.0 1.0 0.9 1.0 1.0 0.7 0.7 0.7 1.0
[97] 0.9 1.0 1.0 0.9 0.9 1.0 1.0 1.0 1.0 1.0 1.0 0.8 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 0.9 1.0 0.7
[121] 1.0 1.0 1.0 0.8 1.0 0.8 1.0 0.6 1.0 1.0 0.9 0.8 0.7 0.9 1.0 1.0 0.5 0.9 1.0 0.8 1.0 0.9 1.0 0.6
[145] 0.9 1.0 0.9 0.9 1.0 0.9 1.0 0.9 1.0 1.0 1.0 1.0 1.0 1.0 1.0 0.7 1.0 1.0 1.0 0.7 0.8 0.9 1.0 1.0
[169] 1.0 1.0
Levels: B M
<- table(predict=fit.knn2, Actual=breast_test_prep$diagnosis)
tt tt
Actual
predict B M
B 106 9
M 1 54
sum(diag(tt)/sum(tt))
[1] 0.9411765
La precisión es peor. Entonces, parece que la precisión (entre otras medidas) nos puede ayudar a determinar cómo escoger \(k\).
Este parámetro se conoce como hiper-parámetro del modelo de predicción, y aquí es donde los métodos de validación cruzada son muy útiles.
Podemos usar la librería caret
para realizar estos cálculos:
<- trainControl(## LOCCV
fitControl method = "loocv")
<- train(diagnosis ~ .,
fit.knn3 data=breast_train_prep,
method="knn",
trControl = fitControl,
tuneLength=10)
# no necesario pero se podría poner
# preProcess = c("center, "scale")
fit.knn3
k-Nearest Neighbors
399 samples
19 predictor
2 classes: 'B', 'M'
No pre-processing
Resampling: Leave-One-Out Cross-Validation
Summary of sample sizes: 398, 398, 398, 398, 398, 398, ...
Resampling results across tuning parameters:
k Accuracy Kappa
5 0.9423559 0
7 0.9448622 0
9 0.9523810 0
11 0.9448622 0
13 0.9423559 0
15 0.9373434 0
17 0.9373434 0
19 0.9373434 0
21 0.9373434 0
23 0.9373434 0
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was k = 9.
plot(fit.knn3)
Podemos obtener otras medidas de capacidad predictiva de la siguiente manera:
<- predict(fit.knn3, newdata=breast_train_prep)
knnPredict confusionMatrix(knnPredict, breast_train_prep$diagnosis)
Confusion Matrix and Statistics
Reference
Prediction B M
B 246 13
M 4 136
Accuracy : 0.9574
95% CI : (0.9327, 0.975)
No Information Rate : 0.6266
P-Value [Acc > NIR] : < 2e-16
Kappa : 0.9078
Mcnemar's Test P-Value : 0.05235
Sensitivity : 0.9840
Specificity : 0.9128
Pos Pred Value : 0.9498
Neg Pred Value : 0.9714
Prevalence : 0.6266
Detection Rate : 0.6165
Detection Prevalence : 0.6491
Balanced Accuracy : 0.9484
'Positive' Class : B
EJERCICIO (Entrega en Moodle: P2-KNN): |
Implementa una función (idealmente podrímo usar el padigma MapReduce) que nos devuelva la precisión del método KNN usando LOOCV para un rango de \(k\) que le de el usuario y que devuelva cuál es el \(k\) óptimo. Úsa esta función para reproducir los resultados del ejemplo anterior (los de la función train() ). |