R语言实现K最近邻算法(KNN)

文章目录


  • 理解近邻分类

  • 第一步收集数据

  • 第二步探索和准备数据

  • 第三步基于数据训练模型

  • 第四步评估模型的性能

  • 第五步提高模型的性能

理解近邻分类

你知道蛋白质、蔬菜和水果是怎么分类的吗?生活中我们发现既不脆也不甜的是蛋白质,脆而不甜的是蔬菜,而水果往往是甜的,有可能脆也有可能不脆。基于以上生活经验(人以群分,物以类聚),那么你知道西红柿是水果还是蔬菜呢?首先我们来看下面一组数据。

食物 甜度 脆度 食物类型 
葡萄 8 5 水果 
四季豆 3 7 蔬菜 
坚果 3 6 蛋白质 
橙子 7 3 水果 

现在如果我们知道西红柿的甜度为6,脆度为4,如果我们把这些数据放在横轴为甜度,纵轴为脆度的二维平面图上,我们很容易计算出西红柿与其他四种食物之间的直线距离。例如西红柿和四季豆之间的距离为:

d(西,)=(63)2+(47)2=4.2d(西红柿,四季豆)=(6−3)2+(4−7)2=4.2

根据以上算法,我们分别计算了西红柿和葡萄、四季豆、坚果、橙子之间的距离,分别是2.2、4.2、3.6、1.4。

我们发现西红柿和橙子之间的距离最短,那么我们据此认为西红柿是一种水果。这里其实是只选了一个最近的“邻居”,即k=1,是一个1NN分类。

如果我们使用k=3的KNN算法。那么它会在三个最近邻居即橙子、葡萄和坚果之间进行投票表决。因为这个里面有两票归为水果(2/3的票数),所以西红柿再次归为水果。

那么k究竟选择多少合适呢?

  • 如果我们选择一个很大的k,会减少噪声数据对模型的影响,或者说减少噪声导致的模型的波动,但是它会使分类器忽视不易察觉但是却很重要的模式风险。比如如果k选择所有观测值的数量,那么原数据中如果水果的占比非常高,那么投票结果也很容易导致西红柿被分到水果类。

  • 如果我们选择一个单一的近邻,这会使得噪声数据或者异常值过度影响模型,导致模型不稳定。

显然,最好的k值应该是两个极端值之间的某个值。在实际中,k一般选择样本量的平方根。比如有15个样本,15的平方根为3.87,我们可以设置k=4。另一种方法是居于各种测试数据集来测试多个k值,并选择一个各异提供最好分类性能的k值。除非噪音非常大,否则大的数据集可以使k值的选择不那么重要,因为参与“投票”的邻居足够多。

KNN算法对数据的要求

如果我们在数据集中加入另外一个特征,比如食物的辛辣度,辛辣度的取值在0~超过100万,而甜度和脆度的取值在1~10之间,所以尺度的差异,导致辛辣对距离函数的影响远远超过了甜度和脆度,如果不对数据进行调整,那么我们可以预见,距离度量和辛辣度有很大的关系,而脆度、甜度的影响几乎可以忽略不计。

解决的方法便是对原数据进行标准化处理,使各个特征的值都落在0~1范围内,或者使各个特征在量上具有可比性。常用的方法有两种:

  • min-max标准化。特征X的每一个值减去它的最小值再除以特征X的极差。这里直接创建一个函数方便后期调用。

    normalize_mm = function(x){	
      return ((x-min(x)) / (max(x)-min(x)))	
    }
  • z-分数标准化。特征X的每一个值减去特征X的均值后,再除以特征X的标准差。创建函数如下:

    normalize_z = function(x){	
      return ((x-mean(x)) / sd(x))	
    }

注意:计算非数值型数据的距离,需要将原数据先转化为数值型数据。一种典型的解决方案使利用虚拟变量编码。例如1表示男性,0表示女性。

第一步收集数据

文中数据来源为威斯康星州乳腺癌数据集,本数据包含699个样本,11个变量。可在 UCI机器学习数据库(http://archive.ics.uci.edu/ml/index.php)中找到

 
 
# 获取原始数据	
loc = "https://archive.ics.uci.edu/ml/machine-learning-databases/"	
ds = "breast-cancer-wisconsin/breast-cancer-wisconsin.data"	
url = paste(loc, ds, sep="")	
breast = read.table(url, sep=",", header=FALSE, na.strings="?")	
	
# 对原始数据添加变量名称	
names(breast) = c("ID", "clumpThickness", "sizeUniformity","shapeUniformity",	
                  "maginalAdhesion","singleEpithelialCellSize", "bareNuclei",	
                  "blandChromatin", "normalNucleoli", "mitosis", "class")	
	
# 设置因子变量,原数据中class编码为2代表良性,4代表恶性	
breast$class = factor(breast$class, levels=c(2,4),	
                  labels=c("benign", "malignant"))


数据集中的变量说明:

  • ID:样本ID号码

  • clumpThickness:肿块厚度

  • sizeUniformity:细胞大小的均匀性

  • shapeUniformity:细胞形状的均匀性

  • maginalAdhesion:边际附着力

  • singleEpithelialCellSize:单个上皮细胞大小

  • bareNuclei:裸核

  • blandChromatin:乏味染色体

  • normalNucleoli:正常核

  • mitosis:有丝分裂

  • class:类别。benign表示良性,malignant表示恶性。

第二步探索和准备数据

 
 
# 清洗数据	
library(tidyverse)	
breast %>% 	
  select(-ID) %>%   # 去掉ID列,此列属于模型无关变量	
  na.omit() ->df  # 缺失值占比很少,此处直接删除	
	
# 对除class列的数据进行标准化处理	
df_n = as.data.frame(lapply(df[1:9], normalize_mm))	
	
# 创建训练数据集和验证数据集	
set.seed(1234)  # 设置随机数种子,方便重复性研究	
train = sample(nrow(df), 0.7*nrow(df))  # 原数据的随机抽取70%用来训练模型	
df_train = df_n[train,]  # df_train为训练数据集	
df_validate = df_n[-train,]  # df_validate为验证数据集	
df_train_labels = df[train, 10] # 训练数据集诊断结果	
df_validate_labels = df[-train, 10] # 验证数据集诊断结果	
	
# 对训练数据和验证数据做初步统计	
df_train %>%	
  cbind(df_train_labels) %>% 	
  rename(class = df_train_labels) %>% 	
  mutate(value = 1) %>%	
  group_by(class) %>%	
  summarise(total = sum(value))->train_stat	
	
df_validate %>%	
  cbind(df_validate_labels) %>%	
  rename(class = df_validate_labels) %>% 	
  mutate(value = 1) %>%	
  group_by(class) %>%	
  summarise(total = sum(value))->validate_stat

第一个变量ID不纳入数据分析,最后一个变量class即输出变量。

对于每一个样本来说,另外九个变量是与判别恶性肿瘤相关的细胞特征,任一变量都不能单独作 为判别良性或恶性的标准,建模的目的是找到九个细胞特征的某种组合,从而实现对恶性肿瘤的 准确预测。第一个变量ID不纳入数据分析,最后一个变量class即输出变量。

数据从UCI数据库中抽取,剔除缺失值,并随机分出训练集和验证集,其中 训练集中包含478个样本单元 (占70%), 其中良性样本单元317个, 恶性样本单元161个; 验证集中包含205个样本单元 (占30%), 其中良性127个, 恶性78个。

第三步基于数据训练模型

 
 
library(class)	
knn.pred = knn(train = df_train,	
               test = df_validate,	
               cl = df_train_labels,	
               k = 22)    # 参数取22是因为训练的样本有478个,开根后是22

第四步评估模型的性能

 
 
library(gmodels)	
CrossTable(x = df_validate_labels,	
           y = knn.pred,	
           dnn = c("Actual", "Predicted"),	
           prop.chisq = FALSE)
 
 
## 	
##  	
##    Cell Contents	
## |-------------------------|	
## |                       N |	
## |           N / Row Total |	
## |           N / Col Total |	
## |         N / Table Total |	
## |-------------------------|	
## 	
##  	
## Total Observations in Table:  205 	
## 	
##  	
##              | Predicted 	
##       Actual |    benign | malignant | Row Total | 	
## -------------|-----------|-----------|-----------|	
##       benign |       125 |         2 |       127 | 	
##              |     0.984 |     0.016 |     0.620 | 	
##              |     0.940 |     0.028 |           | 	
##              |     0.610 |     0.010 |           | 	
## -------------|-----------|-----------|-----------|	
##    malignant |         8 |        70 |        78 | 	
##              |     0.103 |     0.897 |     0.380 | 	
##              |     0.060 |     0.972 |           | 	
##              |     0.039 |     0.341 |           | 	
## -------------|-----------|-----------|-----------|	
## Column Total |       133 |        72 |       205 | 	
##              |     0.649 |     0.351 |           | 	
## -------------|-----------|-----------|-----------|	
## 	
## 

左上角代表真阴性,右下角代表真阳性。预测的准确率为(125+70)/205*100%=95.12%。同时我们也发现位于左下角的8个样本,实际为恶性,但是却被KNN错误地归为良性,即假阴性;右上角2个样本,实际为良性,却被KNN错误地归为恶性,即假阳性。但是预测的准确率还是比较高的,模型令人满意。


第五步提高模型的性能

这里我们可以尝试两种简单的改变,一是数据标准化处理时可以考虑采用z-分数标准化,二是尝试几个不同的k值。需要注意的是,过分的追求预测的精度,可能导致过拟合,加大了拟合噪音的可能,从而使泛化能力变弱。


在确定k值方面,caret包又可以大显身手了。

 
 
library(caret)	
set.seed(1234) # 设置随机数种子,方便重复性研究	
grid = expand.grid(.k = seq(2, 20, by = 1))	
control = trainControl(method = "cv")	
df_validate %>%	
  cbind(df_validate_labels) %>%	
  rename(class = df_validate_labels)->train	
knn.train = train(class~.,	
                  data = train, 	
                  method = "knn",	
                  trControl = control,	
                  tuneGrid = grid)	
knn.train
 
 
## k-Nearest Neighbors 	
## 	
## 205 samples	
##   9 predictor	
##   2 classes: 'benign', 'malignant' 	
## 	
## No pre-processing	
## Resampling: Cross-Validated (10 fold) 	
## Summary of sample sizes: 185, 184, 185, 184, 185, 184, ... 	
## Resampling results across tuning parameters:	
## 	
##   k   Accuracy   Kappa    	
##    2  0.9561905  0.9049269	
##    3  0.9564286  0.9067712	
##    4  0.9466667  0.8857997	
##    5  0.9611905  0.9170926	
##    6  0.9561905  0.9064544	
##    7  0.9511905  0.8953774	
##    8  0.9514286  0.8951847	
##    9  0.9609524  0.9163366	
##   10  0.9609524  0.9163366	
##   11  0.9511905  0.8933637	
##   12  0.9511905  0.8933637	
##   13  0.9511905  0.8933637	
##   14  0.9461905  0.8817695	
##   15  0.9461905  0.8817695	
##   16  0.9511905  0.8933637	
##   17  0.9414286  0.8714247	
##   18  0.9461905  0.8817695	
##   19  0.9464286  0.8830189	
##   20  0.9414286  0.8699629	
## 	
## Accuracy was used to select the optimal model using the largest value.	
## The final value used for the model was k = 5.


报告显示当k=5时Kappa统计量(用于测量两个分类器对观测值分类的一致性)值最高。

下面我们利用k=5重新训练模型:

 
 
knn.pred_new = knn(train = df_train,	
               test = df_validate,	
               cl = df_train_labels,	
               k = 5) 	
CrossTable(x = df_validate_labels,	
           y = knn.pred_new,	
           dnn = c("Actual", "Predicted"),	
           prop.chisq = FALSE)

 
 
## 	
##  	
##    Cell Contents	
## |-------------------------|	
## |                       N |	
## |           N / Row Total |	
## |           N / Col Total |	
## |         N / Table Total |	
## |-------------------------|	
## 	
##  	
## Total Observations in Table:  205 	
## 	
##  	
##              | Predicted 	
##       Actual |    benign | malignant | Row Total | 	
## -------------|-----------|-----------|-----------|	
##       benign |       123 |         4 |       127 | 	
##              |     0.969 |     0.031 |     0.620 | 	
##              |     0.969 |     0.051 |           | 	
##              |     0.600 |     0.020 |           | 	
## -------------|-----------|-----------|-----------|	
##    malignant |         4 |        74 |        78 | 	
##              |     0.051 |     0.949 |     0.380 | 	
##              |     0.031 |     0.949 |           | 	
##              |     0.020 |     0.361 |           | 	
## -------------|-----------|-----------|-----------|	
## Column Total |       127 |        78 |       205 | 	
##              |     0.620 |     0.380 |           | 	
## -------------|-----------|-----------|-----------|	
## 	
## 


我们比较两次结果,我们发现假阴性减少了4个,真阳性增加2个,但是总体上预测的精度还是提高到了(123+74)/205*100%=96.10%,精度提高近1个百分点。

笔者也尝试了利用z-分数标准化对原数据进行处理,根据Kappa统计量确定最优k值为11,结果显示真阴性为123,真阳性为73,假阴性为5,假阳性为4,精度为(123+73)/205*100%=95.61%,精度也是略有提升的。

最后需要指出的是,还有其他方法可以对距离进行加权,kknn包提供了10中不同的加权方式,有兴趣可以尝试。


赞赏作者


640?wx_fmt=png

——————————————

往期精彩:

640?wx_fmt=png

  • 30
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
在MATLAB中实现KNN最近算法可以通过以下步骤进行: 1. 收集和准备数据集:首先,需要收集和准备用于训练和测试的数据集。数据集应包含已知标签的样本。 2. 特征提取和预处理:对于每个样本,需要提取适当的特征,并进行必要的预处理,例如归一化或标准化。 3. 计算距离:对于测试样本,计算其与训练样本之间的距离。常用的距离度量方法包括欧氏距离、曼哈顿距离等。 4. 选择K个最近:根据计算得到的距离,选择K个最近样本。 5. 进行分类:根据K个最近样本的标签,使用投票或加权投票的方式确定测试样本的类别。 在MATLAB中,可以使用自带的knnsearch函数来计算距离和选择最近样本。然后,根据最近样本的标签,可以使用投票或加权投票的方式进行分类。 以下是MATLAB实现KNN最近算法的示例代码: ```matlab % 1. 收集和准备数据集 % 假设训练数据集为trainData,包含特征和标签 % 假设测试数据集为testData,包含特征 % 2. 特征提取和预处理 % 可根据具体情况进行特征提取和预处理操作 % 3. 计算距离 % 假设使用欧氏距离作为距离度量 distances = pdist2(testData, trainData); % 4. 选择K个最近 K = 5; % 假设选择5个最近 \[~, indices\] = mink(distances, K); % 5. 进行分类 % 假设训练数据集的标签为trainLabels % 假设测试数据集的标签为testLabels kNearestLabels = trainLabels(indices); predictedLabels = mode(kNearestLabels, 2); % 使用投票方式进行分类 % 输出预测结果 disp(predictedLabels); ``` 请注意,以上代码仅为示例,实际应用中可能需要根据具体情况进行适当的修改和调整。 #### 引用[.reference_title] - *1* [基于matlab采用KNN算法手写体数字识别实现](https://blog.csdn.net/Taplus/article/details/112996077)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [knn(k近算法)——matlab](https://blog.csdn.net/qq_25990967/article/details/122753558)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值