KNN–K近邻

1、KNN的步骤

  • (1)计算输入数据与训练数据的距离(一般欧几里得距离);
  • (2)从训练集中,选取距离输入数据点最近的k个数据;
  • (3)对于分类任务【常见】,取这k个训练数据类别的众数;对于回归任务,取这k个训练数据值的平均数。
img
特点
  • (1)如上步骤,KNN没有模型训练的过程。需要预测数据时,直接与训练数据集进行计算即可。
  • (2)KNN算法中最重要的超参数就是K的选择,会在下面具体操作中介绍。
  • (3)因为需要计算距离,所以需要进行数值变量标准化,以及类别变量转化(如果有分类变量的话)。
  • (4)KNN在数据量小或者维度较小的情况下效果很好,但不适用于大规模的数据(计算量大)。

关于距离,欧几里得距离,归一化(中心化)

2、mlr建模

2.1 糖尿病数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
library(mlr)
library(tidyverse)
data(diabetes, package = "mclust")

head(diabetes)
#    class glucose insulin sspg
# 1 Normal      80     356  124
# 2 Normal      97     289  117
# 3 Normal     105     319  143
# 4 Normal      90     356  199
# 5 Normal      90     323  240
# 6 Normal      86     381  157

summary(diabetes)
#   class       glucose       insulin            sspg      
# Chemical:36   Min.   : 70   Min.   :  45.0   Min.   : 10.0  
# Normal  :76   1st Qu.: 90   1st Qu.: 352.0   1st Qu.:118.0  
# Overt   :33   Median : 97   Median : 403.0   Median :156.0  
# 				Mean   :122   Mean   : 540.8   Mean   :186.1  
# 				3rd Qu.:112   3rd Qu.: 558.0   3rd Qu.:221.0  
# 				Max.   :353   Max.   :1568.0   Max.   :748.0

2.2 确定预测目标与训练方法

  • (1)确定预测目的:根据三个指标insulin, sspg, glucose 对糖尿病状态class进行诊断
1
2
diabetesTask <- makeClassifTask(data = diabetes, target = "class")
diabetesTask
  • (2)确定预测方法:使用KNN分类算法,超参数设为4
1
2
3
4
5
6
7
knn <- makeLearner("classif.knn", par.vals = list("k" = 4))

#查看mlr可用的算法
listLearners()$class
listLearners("classif")$class
listLearners("regr")$class
listLearners("cluster")$class

2.3 模型训练、预测

  • (1)训练模型:KNN在训练阶段不进行任何计算,直到进入预测阶段之后才进行具体的计算。这在机器学习中是比较少见的,又称为“懒惰”的学习
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 提供原始数据、分类任务
diabetesTask
# 提供机器学习方法
knn

##训练模型
knnModel <- train(knn, diabetesTask)

##模型预测
knnPred <- predict(knnModel, newdata = diabetes)
head(knnPred$data)
#    truth response
# 1 Normal   Normal
# 2 Normal   Normal
# 3 Normal   Normal
# 4 Normal   Normal
# 5 Normal   Normal
# 6 Normal   Normal

##模型评价
performance(knnPred, measures = list(mmce, acc))
#      mmce        acc 
#0.06206897 0.93793103

#混淆矩阵
calculateConfusionMatrix(knnPred)
#            predicted
# true       Chemical Normal Overt -err.-
#   Chemical       32      4     0      4
#   Normal          2     74     0      2
#   Overt           3      0    30      3
#   -err.-          5      4     0      9

2.4 交叉验证

  • 交叉验证是将数据分为两部分:训练集+测试集。在训练集中训练模型,在测试集中评估模型的性能,从而避免过拟合的情况。
  • 如果对交叉验证的结果满意,最后就可以使用所有数据(训练集+测试集)来训练模型。
  • 有3种常见的交叉验证方法:(1)留出法;(2)K-折;(3)留一法。K折交叉验证更常用,演示如下。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
## (1)设置交代交叉验证的方法与具体参数
# "RepCV"表示重复的交叉验证, reps参数交代重复多少次
# folds参数交代k折
# stratify=T 表示在训练集与测试集的数据类别比例一致
kFold <- makeResampleDesc(method = "RepCV", folds = 10, reps = 50, 
                          stratify = TRUE)

## (2)交叉验证
# 上述的设置即为这里的 resampling 参数
kFoldCV <- resample(learner = knn, task = diabetesTask, 
                    resampling = kFold, measures = list(mmce, acc))

## (3)结果评价
kFoldCV$aggr
# mmce.test.mean  acc.test.mean 
# 0.08990934     0.91009066
#查看具体每一次的评价结果
dim(kFoldCV$measures.test)
head(kFoldCV$measures.test)
#   iter       mmce       acc
# 1    1 0.00000000 1.0000000
# 2    2 0.07692308 0.9230769
# 3    3 0.00000000 1.0000000
# 4    4 0.06250000 0.9375000
# 5    5 0.07692308 0.9230769
# 6    6 0.21428571 0.7857143

#计算混淆矩阵(10*10*50)
calculateConfusionMatrix(kFoldCV$pred)
#            predicted
# true       Chemical Normal Overt -err.-
#   Chemical     1506    246    48    294
#   Normal        105   3695     0    105
#   Overt         254      0  1396    254
#   -err.-        359    246    48    653

2.5 调节超参数

  • (1)设置可选的超参数范围以及搜索方法
1
2
3
4
5
# 1到10的10种取值可能
knnParamSpace <- makeParamSet(makeDiscreteParam("k", values = 1:10))

#遍历所有取值可能
gridSearch <- makeTuneControlGrid()
  • (2)比较不同超参数的结果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#定义评价方法:20次重复的10折交叉验证
cvForTuning <- makeResampleDesc("RepCV", folds = 10, reps = 20)

#计算
tunedK <- tuneParams("classif.knn", task = diabetesTask, 
                     resampling = cvForTuning, 
                     par.set = knnParamSpace, 
                     control = gridSearch)
## 最佳超参数
tunedK$x
# $k
# [1] 7

##不同超参数的性能可视化
knnTuningData <- generateHyperParsEffectData(tunedK)
plotHyperParsEffect(knnTuningData, x = "k", y = "mmce.test.mean",
                    plot.type = "line") +
                    theme_bw()

image-20220331170251952

  • (3)使用最佳超参数训练模型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
tunedKnn <- setHyperPars(makeLearner("classif.knn"), par.vals = tunedK$x)
tunedKnnModel <- train(tunedKnn, diabetesTask)

#模型预测
##模拟数据
newDiabetesPatients <- tibble(glucose = c(82, 108, 300), 
                              insulin = c(361, 288, 1052),
                              sspg = c(200, 186, 135))

newDiabetesPatients
##预测分类结果
newPatientsPred <- predict(tunedKnnModel, newdata = newDiabetesPatients)
getPredictionResponse(newPatientsPred)
# [1] Normal Normal Overt 
# Levels: Chemical Normal Overt

2.6 嵌套交叉验证

  • 相对于一般的交叉验证(2.4),嵌套交叉验证更适合于纳入超参数调整等预处理步骤。即在交叉验证中,也去验证超参数调整的步骤。
  • 嵌套交叉验证包括内部循环与外部循环
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
##定义内外循环的嵌套验证策略
#在内部循环中获得交叉验证性能最优的超参数传递给外部循环
inner <- makeResampleDesc("CV") #默认为10折
#在外部循环的训练集使用内部循环给的最佳超参数训练,然后在外部循环的测试集上测试
outer <- makeResampleDesc("RepCV", folds = 10, reps = 5)
knnWrapper <- makeTuneWrapper("classif.knn", resampling = inner, 
                              par.set = knnParamSpace, 
                              control = gridSearch) 

cvWithTuning <- resample(learner=knnWrapper, task=diabetesTask, 
                         resampling = outer,
                         measures = list(mmce, acc))
cvWithTuning
# Aggr perf: mmce.test.mean=0.0910476,acc.test.mean=0.9089524