K 近邻算法(KNN)是机器学习里一个经典的监督学习方法,既能做分类,也能做回归。它原理很简单,却在不少实际场景里表现得不错,比如下面这几种:
- 文本分类:算出文本之间的相似度,就能拿来做文本分类,比如垃圾邮件识别。
- 图像识别:算图像之间的距离,找出最像的那几张,据此判断类别。
- 推荐系统:根据用户的历史行为和偏好,给他推荐兴趣相近的商品或内容。
- 医疗诊断:根据病人的症状和以往病例,预测可能是哪种疾病。
1. 算法原理
KNN 属于懒学习(Lazy Learning)算法。所谓懒,就是训练阶段几乎不做什么计算,只是把训练数据原样存下来。
等到要预测的时候,KNN 才会拿测试点去跟训练集里每个点算距离,挑出最近的 K 个邻居,再根据这 K 个邻居的标签下判断:
- 分类问题:让 K 个邻居各自投票,得票最多的那个类别就是预测结果。
- 回归问题:把 K 个邻居的数值取平均,作为预测结果。

KNN 里最常用的距离是欧氏距离,除此之外也可以用曼哈顿距离、切比雪夫距离等。
K 值是这个算法里最关键的超参数。K 取小了,模型容易过拟合,K 取大了,又可能欠拟合,所以选 K 得讲究办法。常用的做法是交叉验证:在训练集上反复交叉验证,挑出让模型在验证集上表现最好的那个 K。
2. 算法优缺点
先说优点。KNN 的第一个好处是简单易懂,原理很直观,几乎没什么复杂的数学推导,学起来和写起来都不难。而且 KNN 不需要假设数据服从什么分布,这让它在面对一些传统模型难以刻画的复杂分布时,照样能正常工作。
缺点也很明显。头一个是计算开销大。预测时它要把测试点跟训练集里每个点都算一遍距离,数据一多,计算量蹭蹭往上涨,处理大规模数据时成本很高。
其次是存储开销大。KNN 得把整个训练集都存下来备用,数据海量的时候,内存压力也不小。
再有就是 KNN 对噪声很敏感。训练集或测试集里要是混进了噪声或异常点,它们可能被误当成邻居带偏结果,准确率跟着掉下来。数据不干净的时候,KNN 的表现就不太好看。
3. 算法优化
KNN 的计算复杂度会跟着数据维度一起涨。维度一高,KNN 的效率就明显下降,效果还可能被「维度灾难」拖垮。所谓维度灾难,是说特征空间的维度越高,样本点之间的距离就越拉不开差距,距离度量慢慢失效,算法性能也就跟着掉了。
怎么缓解呢?可以用降维方法,比如主成分分析(PCA)。PCA 会找出数据里的主成分,把数据投影到一个更低维的空间里,在保留大部分信息的同时把维度降下来。这样既减轻了计算负担,也能让 KNN 在高维数据上跑得更好。
想让 KNN 在大数据集上查得更快,常用 KD 树(K-dimensional tree)和 Ball 树(Ball tree)这两种数据结构。它们用不同的方式把训练数据组织起来,查邻居时就不必把整个数据集挨个遍历了。
- KD 树:把数据按维度逐层划分的树形结构,每次选一个维度切一刀。查邻居会快不少,在低维数据上尤其好用。
- Ball 树:基于球形区域来划分数据的树形结构,更适合高维数据集,在高维空间里搜邻居效率更高。
用了这些数据结构,KNN 在大数据集上的计算时间能明显缩短,跑起来更利索。
还可以用加权投票法。它跟简单投票不一样,邻居的话语权和它到测试点的距离成反比:离得越近,投票权重越大。这么做是为了压低那些离得远的邻居对结果的干扰,让预测更准。在不少数据集上,加权投票的效果都比简单投票好。
4. 算法使用
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
def iris_data():
"""
鸢尾花数据信息
"""
# 1. 加载鸢尾花数据集
dataset = load_iris()
# 样本数量
print("样本数量:", len(dataset.data))
# 类别名称
print("类别名称:", dataset.target_names)
# 特征名称
print("特征名称:", dataset.feature_names)
# 样本类别
print("样本类别:", dataset.target)
# 样本数据
print("样本数据:", dataset.data)
# knn API
def knn_api():
# 1. 加载鸢尾花数据
iris_dataset = load_iris()
# 2. 创建 knn 分类对象
knn = KNeighborsClassifier(n_neighbors=3)
# 3. 将数据集划分为训练集和测试集[测试集占所有数据集的20%]
X_train, X_test, y_train, y_test = \
train_test_split(iris_dataset.data, iris_dataset.target, test_size=0.2)
# 4. 训练数据
knn.fit(X_train, y_train)
# 5. 进行预测
for index in range(len(X_test)):
# 5.1 计算当前测试样本属于哪个分类
class_label = knn.predict([X_test[index]])
# 5.2 计算当前测试样本属于每个分类的概率
class_proba = knn.predict_proba([X_test[index]])
# 5.3 返回距离当前测试样本最近的 N 个样本
nearest_neighbors = knn.kneighbors([X_test[index]], 5, False)
# 预测准确率
predict_score = knn.score(X_test, y_test, sample_weight=None)
print("准确率:", predict_score)
if __name__ == "__main__":
knn_api()




冀公网安备13050302001966号