背景
K近邻(KNN)是一种常见的监督学习算法,主要用于分类 和回归 问题。在前文《L4 垃圾邮件数据集分类延申 - NB/KNN/SVC/随机森林》中,已经有涉及到适用 KNN 进行自然语言的分类处理。在本文中,将详细介绍 KNN 的模型原理、使用方法等,并且分为分类 与回归分别选取数据集进行模型训练。
两种模型使用的数据集和完整跑通的 ipynb 类型文件可在文章绑定资源中下载获取。
一、算法介绍
1. 基本原理
1.1 概述
KNN 算法基于一个非常简单的思想:给定一个未知样本,找到训练集中与该样本距离最近的 K 个邻居,并根据这些邻居的标签来进行预测。其核心假设是:相似的样本具有相似的标签。
KNN 三要素分别是:距离度量、K 值的选择和分类决策规则。常用的距离度量是欧氏距离及更一般的pL距离。K 值小时,K 近邻模型更复杂;K 值大时,K 近邻模型更简单。K 值的选择反映了对近似误差与估计误差之间的权衡,通常由交叉验证选择最优的 K。它的实现需要考虑如何快速搜索 K 个最近邻点。
1.2 常用距离
1)欧氏距离
欧几里得度量(也称欧氏距离),是一个通常采用的距离定义,指在 m 维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点之间的实际距离。
距离公式:
2)曼哈顿距离
想象你在城市道路里,要从一个十字路口开车到另外一个十字路口,驾驶距离是两点间的直线距离吗?显然不是,除非你能穿越大楼。实际驾驶距离就是这个"曼哈顿距离"。而这也是曼哈顿距离名称的来源,曼哈顿距离也称为城市街区距离(City Block distance)。
距离公式:
3)切比雪夫距离
在数学中,切比雪夫距离(Chebyshev distance)或是L∞度量,是向量空间中的一种度量,二个点之间的距离定义是其各坐标数值差绝对值的最大值。以数学的观点来看,切比雪夫距离是由一致范数(uniform norm)(或称为上确界范数)所衍生的度量,也是超凸度量(injective metric space)的一种。
距离公式:
1.3 模型参数
即设置模型时,KNeighborsClassifier() 的括号内可以设置的参数,存在默认值,需要更改为默认值外的参数时,可以在建模时设定调整。
-
n_neighbors:邻居的数量,即 k的个数,默认值是 5。
-
weights:权衡样本距离对结果影响的方式,可以是 'uniform'(等权重,就说所有的邻近点的权重都是相等的)或 'distance'(基于距离加权,距离近的点比距离远的点的影响大)。默认值是 'uniform'。
-
algorithm:计算相似性的算法,近邻算法,可选 {'auto', 'ball_tree', 'kd_tree', 'brute'}。
-
默认值是'auto',可以理解为算法自己决定合适的搜索算法。
-
brute 是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。
-
kd_tree,构造 kd 树存储数据以便对其进行快速检索的树形数据结构,kd 树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于 20 时效率高。
-
ball_tree 是为了克服 kd 树高纬失效而发明的,其构造过程是以质心 C 和半径 r 分割样本空间,每个节点是一个超球体。
-
-
metric:衡量样本间距离的标准,如'deuclidean'(欧氏距离)、'minkowski'(p-norm)、'manhattan'(曼哈顿距离)等。默认值取决于数据的特征类型,也就是p=2的欧氏距离。
-
leaf_size:KDTree 和 BallTree 构建时叶节点的最大样本数。默认值是30。这个值的设置会影响树构建的速度和搜索速度,同样也影响着存储树所需的内存大小。
-
p:对于Minkowski距离,指定p值(
metric='minkowski'
时)。默认值是2,对应欧氏距离。可以设置为1,使用曼哈顿距离公式进行距离度量。 -
metric_params:针对特定度量的额外参数,距离公式的其他关键参数,这个可以不管,使用默认值 None即可。
-
n_jobs:并行处理的线程数表示最多可用核心数。默认为1,临近点搜索并行工作数。并行设置,如果为-1,那么CPU的所有cores都用于并行工作。
2. 适用场景
以下是 KNN 在分类 和回归任务中分别的适用场景、数据维度和行数的适配性。
2.1 分类
适用场景
- 图像分类:如手写数字识别(MNIST 数据集),KNN 可以在低维空间内通过图像的像素特征进行分类,找到相似的图像类别。
- 文本分类:例如垃圾邮件过滤、情感分析等,KNN 可以根据文档的词频或嵌入特征,找到邻近的类别标签。
- 医学诊断:用于疾病预测,基于病人的生理数据、症状等特征预测疾病类别(例如乳腺癌良性和恶性分类)。
KNN 分类的优缺点
- 优点:简单直观、对数据分布无假设、适合小数据集和低维特征空间、对非线性决策边界有良好表现。
- 缺点:对噪声敏感;大数据时效率低;高维时容易受"维度灾难"影响,距离计算变得不准确。
2.2 回归
适用场景
- 房价预测:例如房价数据集。房价受到多个地理和社会经济特征的影响,KNN 可以根据相邻的房屋价格进行预测。
- 健康指标预测:如根据过去的心率、血压等生理特征预测某一时间的健康指标。KNN 可以利用相似患者的健康数据进行回归预测。
- 金融市场预测:在股票等金融数据中,根据相似的历史数据预测未来的价格或收益率。
KNN 回归的优缺点
- 优点:在局部区域内对非线性关系有较好建模效果,无需假设数据分布;适合处理复杂的非线性问题。
- 缺点:容易受到异常点影响,特别是在稀疏数据上表现不佳。计算成本较高,且对维度的增加敏感。
3. 适用数据集特征
3. 1 特征维度(维数)
KNN 的计算复杂度较高,在处理高维数据时效率低下,并且容易出现"维度灾难",即特征空间维度增加后,距离计算变得不准确,导致模型性能下降。
一般适合低维数据 ,例如在 2 到 20 个维度 的范围内效果较好。超过 20 维的数据集,需要先进行降维(例如使用 PCA、t-SNE 等方法)以减少噪声,降低维数,提升模型效果。
3.2 样本量(行数)
KNN 算法在小数据集上效果较好,因为它需要对每个测试样本计算其与所有训练样本的距离,这会随着样本量的增加而导致计算成本显著上升。
通常适合中小规模数据集,即在几千至几万样本的范围内效果较好;如果样本量达到百万级别,KNN 的预测效率会降低,除非采用更快的距离计算方法或近似算法(例如 KD 树或 Ball 树)进行加速。
4. 总结
场景 | 分类任务 | 回归任务 |
---|---|---|
适用领域 | 图像分类、文本分类、医疗诊断等 | 房价预测、健康指标预测、金融市场预测等 |
特征维度 | 2-20 维效果较佳;适合低维数据,超过 20 维建议降维处理 | 同样适合 2-20 维的低维数据,对于高维回归任务不推荐使用 |
样本量 | 适合几千至几万样本的小数据集,样本量过大需要优化 | 几千至几万样本的小数据集,若数据规模过大需要加速或采用其他方法 |
优缺点总结 | 优点是简单直观、对非线性数据有效;缺点是对噪声敏感、受维度灾难影响 | 优点是能处理非线性关系、在局部效果好;缺点是对异常点敏感,计算成本高,对维度变化敏感 |
二、分类
1. 本部分数据集介绍
1.1 基本信息
Cancer Data 是一个二分类数据集,用于预测乳腺癌肿瘤的类型(良性或恶性)。这个数据集包含 569 条样本和 30 个数值特征。每个特征描述了细胞核图像的几何特征,目标变量是肿瘤的类别。
- 样本数:569
- 特征数:30
- 目标变量:肿瘤类别(良性 or 恶性)
字段介绍:
- ID:样本的唯一标识符(通常在分析中不使用)。
- Diagnosis :诊断结果(标签),表示肿瘤的类型:
- M 表示恶性(Malignant)
- B 表示良性(Benign)
- 除 diagnosis 以外其余 29 个特征,均由分析细胞核的图像得出,包括半径、质地、周长、平滑度、紧致度、凹度、凸点等。
1.2 下载
下载地址:https://www.kaggle.com/datasets/erdemtaha/cancer-data
也可以在文章绑定资源中直接下载获取。
2. 代码
2.1 数据准备
python
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier # KNN
from sklearn.decomposition import PCA #降维-主成分分析
from sklearn.metrics import confusion_matrix, classification_report,accuracy_score
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
%matplotlib inline
# 加载数据集
cancer = pd.read_csv("D:/project/Jupyter/csdn/AI_ML/datasets/L5_1Cancer.csv")
cancer.info()
cancer.head()
cancer.columns
# 删除无意义的第32列
to_drop = ["Unnamed: 32"]
cancer = cancer.drop(cancer[to_drop], axis=1)
# 设定特征和标签
X = cancer.drop(columns=["diagnosis"])
y = cancer["diagnosis"]
# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
2.2 建模
python
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)
knn=KNeighborsClassifier()
knn.fit(X_train,y_train)
y_pred_test_knn=knn.predict(X_test)
# 打印准确率报表,准确率、召回率、F1 值等指标
print(classification_report(y_test,y_pred_test_knn))
2.3 调参
python
# 计算不同 K 值(从 1 到 9)下的模型准确率。
knn_score=[KNeighborsClassifier(n_neighbors=i).
fit(X_train,y_train).score(X_test,y_test) for i in range(1,10)]
# 绘制图表展示 K 值与模型准确率的关系
plt.plot(range(1,10),knn_score)
# 增加权重为 distance,距离越近的邻居对分类结果的影响越大。
knn_score_w=[KNeighborsClassifier(n_neighbors=i,weights="distance").
fit(X_train,y_train).score(X_test,y_test) for i in range(1,10)]
# 绘制图表以观察在基于距离权重下 K 值与准确率的关系
plt.plot(range(1,10),knn_score_w)
# 输出 knn_score_w 和 knn_score 列表中的最大值,用于查看基于距离权重和均匀权重情况下的最佳准确率
max(knn_score_w),max(knn_score)
# 使用曼哈顿距离来计算邻居间的距离
knn_score_w_pl=[KNeighborsClassifier(n_neighbors=i,weights="distance",metric='minkowski',p=1).
fit(X_train,y_train).score(X_test,y_test) for i in range(1,10)]
plt.plot(range(1,10),knn_score_w_pl)
max(knn_score_w_pl)#最高点
2.4 降维
在调参之后对数据进行降维处理,会比无调参步骤直接降维正确率高 2%
python
# 使用 PCA 将数据降至 2 维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
X_train, X_test, y_train, y_test = train_test_split(X_pca, y, test_size=0.2, random_state=42)
# 创建 KNN 模型,选择 K=3
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
# 预测并计算准确率
y_pred = knn.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"KNN 分类模型在降维后数据集上的准确率: {accuracy:.4f}")
三、回归
1. 本部分数据集介绍
1.1 基本信息
红酒质量数据集(Red Wine Quality Dataset)是一个公开可用的数据集,通常用于回归或分类任务,目标是根据红酒的化学特性预测其质量评分,数据集来自葡萄牙维诺·贝尔德红酒的化学分析实验,最早由 Paulo Cortez 等人在研究中提出,记录了红酒的物理化学性质和感官评估得分。
数据量
- 样本数:1599 条记录
- 特征数:10 个(均为数值型数据,包括酸碱度、酒精含量等化学性质数据)
- 目标变量 :quality,即质量评分(整数,范围 0 到 10)
1.2 数据集下载
下载网址:https://www.kaggle.com/datasets/uciml/red-wine-quality-cortez-et-al-2009/data
也可以在文章绑定资源中直接下载获取。
2. 代码
完整代码中包含了数据探索的一些可视化图形,以及更为复杂详细的建立KD树流程,在此部分省略,有兴趣可以下载文章绑定资源查看 ipynb 格式完整代码文件。
2.1 数据准备
python
#Importing required packages.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor # KNN回归
from sklearn.decomposition import PCA #降维-主成分分析
from collections import namedtuple
from pprint import pformat
from math import sqrt
from collections import namedtuple
from operator import itemgetter
from time import process_time
from random import random
from sklearn.neighbors import KDTree
from sklearn.metrics import confusion_matrix, classification_report,mean_squared_error,r2_score
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
%matplotlib inline
# 加载数据集
wine = pd.read_csv("D:/project/Jupyter/csdn/AI_ML/datasets/L5_2RedWine.csv")
wine
wine.info()
2.2 建模
python
# 分离特征和目标变量
X = wine.drop("quality", axis=1)
y = wine["quality"]
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 创建 KNN 回归器,基于默认的欧氏距离
knn = KNeighborsRegressor(n_neighbors=5)
knn.fit(X_train, y_train)
# 预测测试集
y_pred = knn.predict(X_test)
# 计算性能指标
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print(f"MSE: {mse:.2f}, R²: {r2:.2f}")
2.3 优化-KD树/超参数调优
python
# 搜索最佳 n_neighbors 值
mse_scores = []
neighbors_range = range(1, 21)
for n in neighbors_range:
knn = KNeighborsRegressor(n_neighbors=n)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
mse_scores.append(mean_squared_error(y_test, y_pred))
# 绘制 MSE 随邻居数量变化的曲线
plt.figure(figsize=(10, 6))
plt.plot(neighbors_range, mse_scores, marker='o', linestyle='-', color='b')
plt.title("MSE vs. Number of Neighbors")
plt.xlabel("Number of Neighbors")
plt.ylabel("Mean Squared Error")
plt.grid()
plt.show()
# 选择最佳邻居数量
best_n = neighbors_range[np.argmin(mse_scores)]
print(f"Optimal Number of Neighbors: {best_n}")
# 使用优化后的 KNN 建模
# 使用最佳邻居数重新训练模型
knn_optimized = KNeighborsRegressor(n_neighbors=best_n)
knn_optimized.fit(X_train, y_train)
y_pred_optimized = knn_optimized.predict(X_test)
# 计算优化后的性能指标
mse_optimized = mean_squared_error(y_test, y_pred_optimized)
r2_optimized = r2_score(y_test, y_pred_optimized)
print(f"Optimized MSE: {mse_optimized:.2f}, Optimized R²: {r2_optimized:.2f}")
# 构建 KD 树
kd_tree = KDTree(X_train)
# 使用 KD 树的邻近查找加速 KNN
knn_kd = KNeighborsRegressor(algorithm="kd_tree")
knn_kd.fit(X_train, y_train)
y_pred_kd = knn_kd.predict(X_test)
# 评估性能
mse_kd = mean_squared_error(y_test, y_pred_kd)
r2_kd = r2_score(y_test, y_pred_kd)
print(f"KNN with KD Tree - MSE: {mse_kd:.2f}, R²: {r2_kd:.2f}")
2.4 模型比较可视化
python
print(f"Final Optimized MSE: {mse_optimized:.2f}")
print(f"Final Optimized R²: {r2_optimized:.2f}")
# 绘制预测点与真实点的对比散点图
plt.figure(figsize=(12, 6))
# 散点图 - KD 树优化
plt.subplot(1, 2, 1)
plt.scatter(y_test, y_pred_kd, alpha=0.6, label="KD Tree")
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--', label="Ideal")
plt.xlabel("True Quality")
plt.ylabel("Predicted Quality")
plt.title("KD Tree Optimization")
plt.legend()
# 散点图 - 超参数调优
plt.subplot(1, 2, 2)
plt.scatter(y_test, y_pred_optimized, alpha=0.6, label="Optimized")
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--', label="Ideal")
plt.xlabel("True Quality")
plt.ylabel("Predicted Quality")
plt.title("Hyperparameter Optimization")
plt.legend()
plt.tight_layout()
plt.show()
3. 结果分析
将三种模型性能进行比较:
方法 | MSE | R² | 优势 |
---|---|---|---|
初始 KNN | 0.43 | 0.33 | 默认参数,精度一般 |
KD 树优化 | 0.43 | 0.33 | 提升搜索效率,但精度无变化 |
超参数调优 (优化) | 0.40 | 0.39 | 精度提升明显,优化效果最佳 |
结论:
- KD 树更适合大型数据集和实时应用场景,它的优化主要体现在 效率提升,即邻近点搜索速度显著加快,尤其在样本量较大时作用明显。
- 超参数调优 更注重模型性能提升,通过找到最佳参数配置,提高了预测精度,优化效果更显著,因为调整了
n_neighbors
和加权方式,使模型能更好地适配数据分布。 - 如果需要兼顾效率和精度,可以结合两种方法:在 KD 树基础上进行超参数调优。
内容参考
[1] 黄海广.Github 课程项目:机器学习课程
[2] 周志华.机器学习(西瓜书)
[3] 周老师的学生们. 南瓜书