跳至主要内容

使用 R 实现 K-近邻(KNN)分类教程

深入学习使用 R 进行 K-近邻(KNN)分类。了解如何使用 'class' 和 'caret' R 包、调优超参数,并评估模型性能。
· 11分钟 阅读

使用 AI 探索

image2.png

R 是实现 KNN 分类的强大工具,广泛用于数据科学家和统计学家在各类机器学习应用中的实践。

在本教程中,我们将学习 K-近邻(KNN)的概念、工作原理,并回顾其优缺点。此外,我们将使用 R 的 'class' 和 'caret' 包,轻松实现 KNN 分类模型。

什么是 K-近邻(KNN)?

K-近邻(KNN)是一种监督式机器学习模型,可用于回归和分类任务。该算法为非参数模型,这意味着它不对数据的底层分布作出任何假设。

KNN 算法通过在训练数据集的特征空间中,查找与测试数据集中样本最近的邻居,并根据这些邻居的标签来预测测试样本的标签。“K”是最重要的超参数,可以通过调优来优化模型性能。

KNN 是一种简单直观的算法,能在广泛的分类问题上给出良好结果。它容易实现和理解,适用于小型和大型数据集。不过,它也有一些缺点,主要在于对于大规模数据集或高维特征空间,计算代价可能较高。

KNN 广泛应用于电商推荐引擎、图像识别、欺诈检测、文本分类、异常检测等场景。在本教程中,我们将以贷款审批系统为例使用 KNN 算法。

如果您对如何开启数据科学之路仍感到困惑,可查看 Data Scientist Professional with R 职业路径课程,为数据科学职业生涯做好准备。该技能路径将帮助您掌握 R 编程、数据摄取、数据清洗、数据处理、数据可视化、机器学习、假设检验、实验设计、SQL 和 Git。

K-近邻分类如何工作?

KNN 分类算法通过在训练数据集中找到与新数据点最近的 K 个邻居(最近的数据点),然后将邻居中占多数的类别标签赋给新数据点。

让我们将算法分解为多个步骤。

首先,计算新数据点与训练集中所有数据点之间的距离,并选择距离最近的 K 个点。用于计算距离的度量会因问题而异,最常用的是欧氏距离。

在确定了 K 个最近邻之后,算法将这些邻居中占多数的类别标签赋给新数据点。比如,如果两个标签为“blue”,一个标签为“red”,算法会将“blue”赋给新数据点。

Gif from eunsukim.me

动图来源:eunsukim.me

小结:

  1. 选择 K 的取值,即用于预测的最近邻数量。
  2. 计算该点与训练集中所有点的距离。
  3. 根据距离选择最近的 K 个邻居。
  4. 将多数类的标签赋给新数据点。
  5. 对测试集中所有数据点重复步骤 2 至 4。
  6. 评估算法的准确率。

“K” 的值由用户提供,也可用于优化算法性能。K 值过小可能导致过拟合,过大则可能欠拟合。因此,找到既稳定又拟合良好的最优值至关重要。

在 R 中实现 KNN

本节中,我们将使用 贷款数据,并借助 class 包训练 KNN 分类模型。该数据集包含 10,000 条贷款记录,我们将根据客户数据判断贷款是否会按期还清。

加载数据

我们将导入 tidyverse 库,以访问数据加载、处理和可视化所需的 R 基础包。suppressPackageStartupMessages 将屏蔽启动信息,使输出更干净。

随后,我们使用 read_csv 加载数据集,使用 subset 函数从数据框中移除 “purpose” 列,并展示前 3 条样本。

suppressPackageStartupMessages(library(tidyverse))

data <- read_csv('data/loans.csv.gz', show_col_types = FALSE)
data <- subset(data, select = -c(purpose))
head(data,3)

image4.png

划分训练集与测试集

我们可以手动划分数据集,但使用 caTools 库会更简洁。

  • 设置随机种子以保证可复现性。
  • 使用 sample.split 以 75:25 的比例为训练集和测试集创建索引。
  • 使用 subset 创建训练集与测试集,如下所示。
library(caTools)
set.seed(255)

split = sample.split(data$not_fully_paid, 
                     SplitRatio = 0.75)
train = subset(data, 
                      split == TRUE)
test = subset(data, 
                  split == FALSE)

特征缩放

现在对训练集和测试集进行缩放。底层实现采用 (x - mean(x)) / sd(x)。我们只对特征做缩放,并从训练集与测试集中移除目标标签。

train_scaled = scale(train[-13])
test_scaled = scale(test[-13])

训练 KNN 分类器并预测

class 库在训练 KNN 分类方面非常常用,简单且快速。我们将提供缩放后的训练/测试数据集、目标列以及 “k” 超参数。

library(class)
test_pred <- knn(
                 train = train_scaled, 
                 test = test_scaled,
                 cl = train$not_fully_paid, 
                 k=10
                 )

模型评估

为评估模型结果,我们将使用 table 函数展示混淆矩阵。我们向 table 函数提供了实际(测试目标)与预测标签。可以看到,对多数类的预测效果相当不错。

KNN 不擅长处理类别不平衡的数据,这也是我们在少数类上表现较差的原因。

actual <- test$not_fully_paid

cm <- table(actual,test_pred)
cm
     test_pred
actual    0    1
     0 1988   23
     1  373   10

我们可以通过将混淆矩阵中的对角线(真正类)求和,并除以目标列总长度来计算准确率。

可以看到,在未调优的基础模型上,我们已获得较好的准确率。通过调优 “K” 超参数并对数据集做平衡处理,还可进一步提升准确率。

accuracy <- sum(diag(cm))/length(actual)
sprintf("Accuracy: %.2f%%", accuracy*100)
'Accuracy: 83.46%'

使用 caret 在 R 中进行 KNN 分类

本节我们将全程使用 caret。caret 是一个用于构建和评估机器学习模型的 R 包,为主流机器学习算法提供统一接口。

我们将用它来划分与预处理数据集、执行超参数调优,并训练与评估模型。

划分训练集与测试集

我们将导入 caret 包并设置随机种子以保证可复现性。随后把目标变量从整数转换为因子。最后使用 createDataPartition 按 80:20 的比例将数据集划分为训练集和测试集。

suppressPackageStartupMessages(library(caret))
set.seed(255)

data$not_fully_paid <- factor(data$not_fully_paid, levels = c(0, 1))

trainIndex <- createDataPartition(data$not_fully_paid, 
								  times=1, 
								  p = .8, 
								  list = FALSE)
train <- data[trainIndex, ]
test <- data[-trainIndex, ]

数据预处理

随后我们使用 preProcess 函数对训练集与测试集进行缩放。

preProcValues <- preProcess(train, method = c("center", "scale"))
trainTransformed <- predict(preProcValues, train)
testTransformed <- predict(preProcValues, test)

模型调优

在训练 KNN 模型之前,我们需要通过训练函数找到 “K” 的最优值。train 函数需要提供公式、缩放后的训练数据集、模型名称、训练控制方法(交叉验证)以及超参数列表。我们将评估 K 取 3、5、7 时的模型表现。

knnModel <- train(
		     not_fully_paid ~ ., 
		     data = trainTransformed, 
	            method = "knn", 
		     trControl = trainControl(method = "cv"), 
		     tuneGrid = data.frame(k = c(3,5,7))
		    )

训练表现最佳的模型

找到 “K” 的最佳取值后,我们将使用缩放后的训练数据集训练 KNN 分类模型。

best_model<- knn3(
                  not_fully_paid ~ .,
                  data = trainTransformed,
                  k = knnModel$bestTune$k
                 )

模型评估

caret 提供了简单而强大的模型评估函数。为了展示模型表现,我们先对未见过的测试数据集进行标签预测。之后使用预测值与实际值,通过 confusionMatrix 函数评估模型性能。

predictions <- predict(best_model, testTransformed,type = "class")
# Calculate confusion matrix
cm <- confusionMatrix(predictions, testTransformed$not_fully_paid)
cm

结果中包含混淆矩阵、模型准确率、P 值、模型敏感性以及其他有助于判断模型稳定性和性能的重要指标。

如我们所见,模型在“Neg Pred Value”(少数类)上的表现较差,平衡准确率为 51%,与掷硬币的结果相近。

我们可以通过过采样和欠采样来平衡类别,从而改善结果。也可以进行特征工程,创建新特征并删除高度相关的特征。

Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1570  288
         1   39   18
                                          
               Accuracy : 0.8292          
                 95% CI : (0.8116, 0.8458)
    No Information Rate : 0.8402          
    P-Value [Acc > NIR] : 0.9091          
                                          
                  Kappa : 0.0516          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.97576         
            Specificity : 0.05882         
         Pos Pred Value : 0.84499         
         Neg Pred Value : 0.31579         
             Prevalence : 0.84021         
         Detection Rate : 0.81984         
   Detection Prevalence : 0.97023         
      Balanced Accuracy : 0.51729         
                                          
       'Positive' Class : 0

我们也可以将结果以数据框的形式简化展示。

data.frame(Accuracy = cm$overall["Accuracy"],
           Sensitivity = cm$byClass["Sensitivity"],
           Specificity = cm$byClass["Specificity"])

image3.png

如果您更偏爱 Python,想学习如何进行 KNN 分类,请阅读我们的 使用 scikit-learn 的 K-近邻(KNN)分类 教程,通过示例理解 KNN 的概念与流程。

使用 KNN 的优缺点

优点

  1. 算法简单,易于理解和实现。
  2. 适用性强,可用于回归与分类任务。
  3. 结果具有可解释性,易于可视化与理解,因为预测类别基于训练数据中最近邻的标签。
  4. KNN 不对类别间的决策边界作假设,因此能够捕捉特征之间的非线性关系。
  5. 算法不对数据分布作出假设,适用于多种问题场景。
  6. KNN 不构建显式模型,而是存储训练数据并在预测时使用。

缺点

  1. 对于大型、复杂数据集,计算与内存开销较大。
  2. 在类别不平衡的数据上性能下降,易偏向多数类,从而导致对少数类的表现较差。
  3. 不适合噪声较大的数据,因为数据点的最近邻可能并不能代表其真实类别。
  4. 不适用于高维数据,高维度可能导致各点之间的距离变得相似。
  5. 寻找最优的 K 值可能耗时。
  6. KNN 对离群点敏感,因为其以距离度量来选择邻居。
  7. 不善于处理训练数据中的缺失值。

结论

在本教程中,我们学习了如何在 R 中使用 K-近邻(KNN)分类。我们介绍了 KNN 的基本概念与工作原理,并学习了 class 与 caret 两个库如何在真实数据集上训练与评估 KNN 分类模型。

通过 Supervised Learning in R: Classification 课程,您还可学习 R 中的其他监督学习算法。课程将通过示例与编码练习讲解朴素贝叶斯、逻辑回归与分类树。

本 R 教程涵盖了数据处理、划分与预处理、超参数调优、模型训练与结果评估等步骤。由于数据集不平衡,我们在少数类上的表现最差。

需要注意的是,KNN 并不完美,它同样存在一些缺点。在将其作为主要模型之前,我们需要综合考虑多方面因素。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题