基本原理
首先放一张各大网站用烂的图
KNN的基本思想是比较简单的,就是假设我们有红色和蓝色得到数据点,然后我们新加入一个绿色点,根据绿色点最近的点是红色最多还是蓝色最多,这个用距离来衡量(常用的是欧式距离),来判断绿色的点属于哪个类别,而附近点的数目取多少(即k值大小)来判断,也是比较重要的,一般来说网上看到很多都是默认30或20以下,1~20左右,然后可以用k=1-20,分别拟合预测数据,来根据预测结果的好坏选择合适的k值。数据维度比较高的话,我们可以先用PCA降维,再用KNN分类预测。不过,如果你的训练集当中,某一类别的占比非常之高,这就可能造成很大的误差,这时候用KNN怎么优化预测结果大概率都是占比高的那个类别,因此在这种情况下,最好不要使用KNN。
R应用
R的话,做knn可以用class里面的knn函数,或者用caret里面的train函数,method设置成knn。因为caret可以直接进行交叉验证根据结果选择最佳k值,因此用其是比较方便的。
以下提供了两种包的实现方式,使用来自bioconductor的白血病基因表达数据集。
rm(list=ls())
library(leukemiasEset) # 白血病数据集
library(class) # 分类用的R包,包含一个简易的knn
library(caret) # 同样包含knn,但功能更加全面强大,可以直接进行交叉验证
library(purrr) # 方便手动写迭代函数,计算best k。
data("leukemiasEset")
exprdata <- exprs(leukemiasEset)
exprtrain <- as.data.frame(t(exprdata))
# PCA
pca.exprtrain <- prcomp(exprtrain, scale = TRUE)
cumvar <- round(cumsum(pca.exprtrain$sdev**2/sum(pca.exprtrain$sdev**2))*100, 2)
names(cumvar) <- paste("PC", 1:length(cumvar), sep="")
# Select the least number of principal components so that the sum of the cumulative proportion of variance is >95%
inputSet <- as.data.frame(pca.exprtrain$x[, 1:which(cumvar>95)[1]])
inputSet$type <- leukemiasEset$LeukemiaType
# KNN
# By manually dividing the data set into a training set and a test set by 7:3
train.ind <- sample(1:nrow(inputSet), 0.7*nrow(inputSet), replace = FALSE)
train <- inputSet[train.ind, ]
x.train <- train[, -ncol(train)]
y.train <- train$type
test <- inputSet[-train.ind, ]
x.test <- test[, -ncol(test)]
y.test <- test$type
bestknn <- function(kvalue){
knn.predict <- knn(x.train, x.test, y.train, k=kvalue)
confusion <- as.matrix(table(knn.predict, y.test))
acc <- sum(diag(confusion))/sum(confusion)
}
knnacc <- map_dbl(1:20, bestknn)
# best k
which(knnacc == max(knnacc))[length(which(knnacc == max(knnacc)))]
# Run knn with caret and perform k-fold cross-validation
control <- trainControl(method="cv", number=5, savePredictions = TRUE, classProbs = TRUE)
set.seed(123)
knn.caret <- train(type~., data=train, method="knn",
metric="Accuracy", trControl=control, tuneGrid=expand.grid(k=seq(1, 20)))
knn.caret
knnPredict <- predict(knn.caret, newdata=test)
confusionMatrix(knnPredict, y.test)
Python应用
Python的话为了简便起见用了手写数字数据集来先降维后预测,同样也是分为7:3分割数据集的模式和交叉验证的模式,选取k=1-20,分别计算准确率,取使其最高的k。
from sklearn import datasets
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score
import numpy as np
digits = datasets.load_digits()
# PCA
pca = PCA(digits.data.shape[1])
pca.fit(digits.data)
n_index = np.where(np.cumsum(pca.explained_variance_ratio_) > 0.95)[0][0]
digits_pca = pca.transform(digits.data)[:, 0:n_index+1]
x_train, x_test, y_train, y_test = train_test_split(
digits_pca,
digits.target,
train_size=0.3,
stratify=digits.target
)
# KNN with 7:3 training and test sets
acc1 = []
for k in range(1, 21):
knn = KNeighborsClassifier(k)
knn.fit(x_train, y_train)
y_pred = knn.predict(x_test)
acc1.append(accuracy_score(y_test, y_pred))
k_best1 = np.where(acc1==max(acc1))[0][0]+1
# KNN with cv
acc2 = []
for k in range(1, 21):
knn = KNeighborsClassifier(k)
scores = cross_val_score(knn, digits_pca, digits.target, cv=10, scoring="accuracy")
acc2.append(scores.mean())
k_best2 = np.where(acc2==max(acc2))[0][0]+1