引言
本章我们正式开使学习机器学习,首先我们要认识一个机器学习中最基本的算法:K-近邻(KNN)算法是一种直观的、基于实例的经典机器学习方法。简单来说,要判断一个新样本的类别,需考察它在特征空间中距离最近的K个已知样本的类别,并遵从多数邻居的类别进行归属。该算法不需要显式训练,直接存储数据,预测时通过计算距离(如欧氏距离)来寻找最近邻;而K值的选择至关重要,过小易受噪声干扰,过大则可能模糊边界。
一、KNN算法深度解析
1、KNN算法的工作原理
KNN算法的工作原理为:首先,计算新数据点与数据集中每一个已知样本点之间的距离(通常使用欧氏距离等度量方法);接着,根据计算出的距离,从所有已知点中找出距离最近的K个样本作为"邻居"(这里的K是一个需要预先设定的关键参数);然后,统计这K个近邻中各个类别分别出现的频率;最后,根据"少数服从多数"的原则,将出现次数最多的那个类别判定为新数据点所属的类别。
一般三种距离计算公式:
1)、 欧式距离
二维空间:√[(x₂-x₁)² + (y₂-y₁)²]
n维空间:√[∑(x₂_i - x₁_i)²]
欧式距离是KNN算法中最常用的距离度量方式,其本质是计算在特征空间中两个点之间的"直线距离"。在二维空间中,距离公式为 √[(x₂-x₁)² + (y₂-y₁)²],即两点横纵坐标差值的平方和再开平方根,在n维空间中,两个点之间的欧式距离是它们在每个维度上差值平方之和的平方根,表示为 √[∑(x₂_i - x₁_i)²]。距离值越小,说明两个样本点在特征空间中的位置越接近,其特征也越相似,这是KNN算法进行"近邻"判断的数学基础。
# 计算两个点之间的欧式距离
import math
def euclidean_distance(point1, point2): #计算两点之间的欧式距离
distance = 0
for i in range(len(point1)):
distance += (point1[i] - point2[i]) ** 2
return math.sqrt(distance)
point_a = [1, 2, 3]
point_b = [4, 5, 6]
print(f"A点到B点的欧式距离:{euclidean_distance(point_a, point_b):.2f}")

2)、 曼哈顿距离
二维空间:|x₂-x₁| + |y₂-y₁|
n维空间:∑|x₂_i - x₁_i|
曼哈顿距离是KNN算法中另一种核心的距离度量方式。它是计算两点在标准坐标系上的绝对轴距之和,而非直线距离。在二维空间中,其公式为 `|x₂ - x₁| + |y₂ - y₁|`,意思是从一点到另一点沿着平行于坐标轴的路径行走的总路程,其通式为所有维度上坐标差值绝对值的总和 `∑|x₂_i - x₁_i|`。曼哈顿距离衡量的是"网格路径"长度,因此在数据特征遵循网格状分布、或各维度影响具有可加性的场景中更为适用。
3)、切比雪夫距离
二维空间:max(|x₂-x₁|, |y₂-y₁|)
n维空间:max(|x₂_i - x₁_i|)
切比雪夫距离是KNN算法中一种特殊的距离度量,它定义为两个点在所有坐标维度上绝对差值的最大值。在二维空间中,其计算公式为 `max(|x₂-x₁|, |y₂-y₁|)`,即在横坐标差值绝对值与纵坐标差值绝对值中取最大者;其通式为在所有维度i上计算坐标差值绝对值,然后取其中的最大值 `max(|x₂_i - x₁_i|)`。这种距离度量直观上可以理解为两点在任意单一维度上可能存在的最大差距,它适用于那些变化为最大维度差异的场景,例如图像处理。
2、KNN算法的优缺点
KNN算法作为经典分类方法,其核心优势在于原理直观、易于理解,遵循"物以类聚"的思想,并且无需显式的训练过程,直接存储数据,同时它对异常值相对不敏感,在许多实际分类问题上能取得良好的效果。然而,该算法也存在明显局限:由于需要在预测时计算新样本与所有训练样本的距离,其计算开销随数据量增长而急剧增大;在高维场景下,数据稀疏性会削弱其效果;算法的性能对关键参数K值的设定十分敏感,选择不当易导致过拟合或欠拟合;此外,当训练数据存在明显类别不平衡时,多数表决的机制会使分类结果偏向多数类,影响对少数类的识别能力。
二、KNN算法API参数详解
1、sklearn.neighbors.KNeighborsClassifier 完整的参数
from sklearn.neighbors import KNeighborsClassifier
# API完整格式:
# class sklearn.neighbors.KNeighborsClassifier(
# n_neighbors=5, # K值,最重要的参数
# weights='uniform', # 权重函数
# algorithm='auto', # 计算最近邻的算法
# leaf_size=30, # KD树或球树的叶子大小
# p=2, # 距离度量参数
# metric='minkowski', # 距离度量标准
# metric_params=None, # 距离度量的额外参数
# n_jobs=None, # 并行计算
# **kwargs
# )
2、关键参数详解
1)、n_neighbors(K值)
在KNN算法中,n_neighbors(K值)是一个重要的参数,它决定了在进行预测时需要参考的"最近邻居"的数量。其作用是控制模型的复杂度和稳定性:较小的K值会使模型对局部数据模式更敏感,决策边界更细致,但容易受到噪声和异常点的干扰,导致模型不稳定和过拟合;较大的K值则会使模型考虑更广泛的邻居,而产生更平滑、更稳定的决策边界,但可能忽略数据的局部细节,导致模型过于简单而欠拟合。常见的K值通常选择3到10之间的奇数。原因是为了在二分类任务中避免"平票"(例如,当K=4时,可能出现两个邻居属于A类,两个属于B类的情况)。
2)、weights(权重)
在权重的设置中,主要有三种方式:第一种是均匀(uniform)权重,此为默认选项,它赋予所有邻居样本完全相等的权重;第二种是距离(distance)权重,此方式下权重与距离成反比,即样本点距离待预测点越近,其影响力或权重就越大;此外,我们也可以提供自定义函数来完全自主地定义权重的计算规则。
3)、algorithm(算法选择)
在算法的选择中提供了多种策略以应对不同的场景:默认选项为自动(auto),系统将根据数据特征自动选择最合适的算法;若数据维度较高,可选用球树(ball_tree)算法;对于低维数据,KD树(kd_tree)算法通常更为高效;而当处理的数据集规模较小时,则可以直接使用暴力搜索(brute)方法进行计算。
4)、p(距离度量参数)
距离度量的参数p决定了计算样本间距离的具体公式:当p=1时,采用曼哈顿距离;默认设置为p=2,即使用最常见的欧氏距离;若将p设置为大于2的数值,则对应闵可夫斯基距离的更一般形式。
5)、metric(距离度量标准)
在距离度量的标准(metric)参数中,除了默认的闵可夫斯基距离(minkowski)之外,系统还提供了其他几种常见的距离度量方式供选择,包括:欧氏距离(euclidean)、曼哈顿距离(manhattan)以及切比雪夫距离(chebyshev)。
三、KNN算法实战案例:学生类型分类
KNN算法主要就上面这些知识,接下来博主将直接从代码上来解释如何使用KNN算法。
1、导入必要的库
首先我们要导入我们会用到的库,方便我们后续的编程。
# 导入必要的库
import numpy as np # 用于数值计算和矩阵操作
import matplotlib.pyplot as plt # 用于数据可视化
from sklearn.neighbors import KNeighborsClassifier # KNN算法实现
2、加载并查看数据
在设置KNN模型时,关键参数包括权重的三种计算方式、算法的四种选择策略,以及距离度量的相关设置。代码中,使用np.loadtxt加载数据文件后,可通过.shape和切片查看数据维度与前几行,并通过data[:, -1]提取标签列,最后利用np.unique统计各类别的样本数量以了解数据分布。
# 加载数据文件
data = np.loadtxt('datingTestSet2.txt')
# 查看数据基本信息
print("数据基本信息:")
print(f"数据形状:{data.shape}") # 显示数据的行数和列数
print(f"数据预览(前5行):")
print(data[:5]) # 显示前5行数据
# 统计各类别样本数量
labels = data[:, -1] # 获取最后一列(标签列)
unique_labels, counts = np.unique(labels, return_counts=True)
print(f"\n各类别样本数量:")
for label, count in zip(unique_labels, counts):
print(f"类别{int(label)}:{count}个样本")

3、数据可视化分析
在数据可视化分析阶段,首先依据数据的类别标签,通过布尔索引将数据集划分为三个子集,随后通过matplotlib创建3D图形:利用plt.axes(projection="3d")设置三维坐标系,并分别用不同颜色、标记为每类数据绘制散点图,其中各点的x、y、z轴对应三个特征变量。接着,设置坐标轴标签与图形标题,添加图例以说明类别对应关系,最终调用plt.show()呈现出一幅直观展示不同类别样本在三维特征空间分布状况的可视化图形。
# 将数据按类别分开
data_1 = data[data[:, -1] == 1] # 类别1的数据
data_2 = data[data[:, -1] == 2] # 类别2的数据
data_3 = data[data[:, -1] == 3] # 类别3的数据
# 创建3D可视化图形
fig = plt.figure(figsize=(10, 8)) # 创建图形窗口,设置大小
ax = plt.axes(projection="3d") # 设置为3D坐标轴
# 绘制散点图
ax.scatter(data_1[:, 0], data_1[:, 1], zs=data_1[:, 2],
c="#00DDAA", marker="o", label="爱学习型", s=30)
ax.scatter(data_2[:, 0], data_2[:, 1], zs=data_2[:, 2],
c="#FF5511", marker="^", label="一般型", s=30)
ax.scatter(data_3[:, 0], data_3[:, 1], zs=data_3[:, 2],
c="#000011", marker="+", label="爱玩型", s=30)
# 设置坐标轴标签
ax.set_xlabel("每年旅行路程(km)")
ax.set_ylabel("游戏时间百分比(%)")
ax.set_zlabel("每周零食消耗(kg)")
ax.set_title("学生特征3D分布图")
# 添加图例
ax.legend()
# 显示图形
plt.show()

4、准备训练数据
在准备KNN模型的训练数据时,首先需要将原始数据集明确划分为特征矩阵和标签向量:通过data[:, :-1]提取所有样本的全部特征作为矩阵X,其中:表示选择所有行,:-1表示选取除最后一列外的所有列;同时,通过data[:, -1]获取最后一列的类别信息作为标签向量y。最后可进一步通过检查.shape属性确认数据的维度,确保数据被正确分割并准备好用于后续的模型训练。
# 分离特征和标签
X = data[:, :-1] # 所有行,除了最后一列的所有列(特征)
y = data[:, -1] # 所有行,只取最后一列(标签)
print("特征数据X的形状:", X.shape)
print("标签数据y的形状:", y.shape)
print("\n特征数据示例(前3个样本):")
print(X[:3])
print("\n对应的标签:")
print(y[:3])
5、创建并训练KNN模型
接下来进入模型构建:首先将数据集明确划分为特征矩阵X和标签向量y,其中X包含所有样本的前三列特征,y则为对应的类别标签。随后,通过设置KNeighborsClassifier的关键参数(如设定最近邻居数K=5、采用均匀权重、自动选择最优算法及使用欧氏距离)来创建KNN分类器实例,并调用fit方法完成模型训练。
# 创建KNN分类器
knn_model = KNeighborsClassifier(
n_neighbors=5, # K值,考虑最近的5个邻居
weights='uniform', # 权重方式:所有邻居权重相等
algorithm='auto', # 算法选择:自动选择最优算法
p=2 # 距离度量:p=2表示欧式距离
)
# 训练模型
knn_model.fit(X, y)
print("KNN模型训练完成!")
print(f"使用的参数:K={knn_model.n_neighbors}, 距离度量={'欧式距离' if knn_model.p==2 else '曼哈顿距离'}")
6、使用模型进行预测
在训练好模型之后,用训练好的模型对四个新学生的特征数据进行预测,模型根据最近邻原则输出数字标签,再通过条件判断将数字转换为"爱学习型"、"一般型"或"爱玩型"的文字描述,直观展示每个新学生的特征与其预测类型之间的对应关系。
# 定义4个新学生的特征数据
new_students = [
[9744, 11.440364, 0.760461], # 学生1
[16191, 0.100000, 0.605619], # 学生2
[42377, 6.519522, 1.058602], # 学生3
[27353, 11.475155, 1.528626] # 学生4
]
# 进行预测
predictions = knn_model.predict(new_students)
# 显示预测结果
print("新学生类型预测结果:")
print("-" * 40)
for i, (student_features, pred) in enumerate(zip(new_students, predictions), 1):
# 将数字标签转换为文字描述
if pred == 1:
student_type = "爱学习型"
elif pred == 2:
student_type = "一般型"
else:
student_type = "爱玩型"
print(f"学生{i}:")
print(f" 特征:旅行{student_features[0]}km, "
f"游戏时间{student_features[1]}%, "
f"零食消耗{student_features[2]}kg")
print(f" 预测类型:{student_type}")
print()

7、K值选择实验
我们可以定义不同的K值来观察结果的变化。
# 实验不同的K值对预测结果的影响
print("K值选择实验")
print("=" * 50)
# 定义不同的K值
k_values = [1, 3, 5, 7, 9, 11, 15]
print("K值 | 学生1预测 | 学生2预测 | 学生3预测 | 学生4预测")
print("-" * 50)
for k in k_values:
# 使用不同的K值创建模型
knn_k = KNeighborsClassifier(n_neighbors=k)
knn_k.fit(X, y)
# 预测
preds = knn_k.predict(new_students)
# 将数字标签转换为文字
pred_names = []
for pred in preds:
if pred == 1:
pred_names.append("爱学习")
elif pred == 2:
pred_names.append("一般")
else:
pred_names.append("爱玩")
print(f"{k:2d} | {pred_names[0]:^8s} | {pred_names[1]:^8s} | {pred_names[2]:^8s} | {pred_names[3]:^8s}")
print("\n分析:")
print("1. K=1时,模型可能过拟合,只考虑最近的一个邻居")
print("2. K=5时,平衡了局部和全局信息")
print("3. K太大时,可能欠拟合,忽略了局部特征")

到这里我们就基本完成了KNN算法的相关学习,在下一章里博主将会和大家开始机器学习中线性回归和逻辑回归的学习。